const cfg = window.APP_CONFIG;
const configured = cfg.supabaseUrl && !cfg.supabaseUrl.includes('PASTE_') && cfg.supabaseAnonKey && !cfg.supabaseAnonKey.includes('PASTE_');
const sb = configured ? window.supabase.createClient(cfg.supabaseUrl, cfg.supabaseAnonKey) : null;
let session=null, profile=null, currentView='home', authMode='login', activePatientId=null;
let viewHistory=[];
let pendingRestoreScrollY=null;
let activeInactivePatientId=null, inactiveInvitationToken=null;
let activeFamilyMemberId=null, activeAdminFamilyMemberId=null;
let adminAgendaWeekOffset=0;
let editingBenefitId=null;
let editingCouponId=null,editingCampaignId=null;
let promotionDetailCache=new Map();
let promotionRedemptionCache=new Map();
let authenticatedUserId=null;
let billingPrefillPatientId=null,billingPrefillFamilyMemberId=null;
let billingMonth=new Date().toISOString().slice(0,7);
const ADMIN_CONTEXT_KEY='miSaludAdminContextV49';
let restorePatientScrollY=0;

function captureNavigationState(){
  return {
    view:currentView,
    scrollY:window.scrollY||0,
    activePatientId,
    activeInactivePatientId,
    activeFamilyMemberId,
    activeAdminFamilyMemberId,
    billingPrefillPatientId,
    billingPrefillFamilyMemberId
  };
}
function pushNavigationState(){
  const state=captureNavigationState();
  const last=viewHistory[viewHistory.length-1];
  if(last&&last.view===state.view&&last.activePatientId===state.activePatientId&&last.activeInactivePatientId===state.activeInactivePatientId){last.scrollY=state.scrollY;return;}
  viewHistory.push(state);
  if(viewHistory.length>30)viewHistory.shift();
}
async function navigateTo(view,{remember=true}={}){
  if(remember)pushNavigationState();
  if(currentView==='patient-detail'&&view!=='patient-detail')clearAdminContext();
  if(view!=='patient-detail')activePatientId=null;
  if(view!=='inactive-patient-detail')activeInactivePatientId=null;
  currentView=view;
  await render();
}
async function navigateBack(){
  const state=viewHistory.pop();
  if(!state){
    if(currentView==='patient-detail'){activePatientId=null;clearAdminContext();currentView='patients';}
    else if(currentView==='inactive-patient-detail'){activeInactivePatientId=null;currentView='inactive-patients';}
    else currentView='home';
    pendingRestoreScrollY=0;
    await render();
    return;
  }
  currentView=state.view||'home';
  activePatientId=state.activePatientId||null;
  activeInactivePatientId=state.activeInactivePatientId||null;
  activeFamilyMemberId=state.activeFamilyMemberId||null;
  activeAdminFamilyMemberId=state.activeAdminFamilyMemberId||null;
  billingPrefillPatientId=state.billingPrefillPatientId||null;
  billingPrefillFamilyMemberId=state.billingPrefillFamilyMemberId||null;
  pendingRestoreScrollY=Number(state.scrollY||0);
  await render();
}
function saveAdminContext(){
  if(profile?.role!=='admin'||currentView!=='patient-detail'||!activePatientId)return;
  try{localStorage.setItem(ADMIN_CONTEXT_KEY,JSON.stringify({userId:session?.user?.id||null,patientId:activePatientId,view:'patient-detail',scrollY:window.scrollY||0,savedAt:Date.now()}));}catch(e){}
}
function clearAdminContext(){
  try{localStorage.removeItem(ADMIN_CONTEXT_KEY);}catch(e){}
  restorePatientScrollY=0;
}
function restoreAdminContext(){
  if(profile?.role!=='admin'||!session?.user?.id)return;
  try{
    const raw=localStorage.getItem(ADMIN_CONTEXT_KEY);if(!raw)return;
    const state=JSON.parse(raw);
    const fresh=Date.now()-Number(state.savedAt||0)<60*60*1000;
    if(fresh&&state.userId===session.user.id&&state.view==='patient-detail'&&state.patientId){
      activePatientId=state.patientId;currentView='patient-detail';restorePatientScrollY=Number(state.scrollY||0);
    }else clearAdminContext();
  }catch(e){clearAdminContext();}
}
let appSettings={plan_name:'Servicios y Beneficios',membership_price:null,virtual_contacts_per_month:0,plan_description:'Citas, documentos, controles, campañas, cupones y atención médica desde un solo lugar.'};

const FAMILY_CONTEXT_KEY='medicoEnCasaFamilyContextV1';
function saveFamilyContext(){try{localStorage.setItem(FAMILY_CONTEXT_KEY,activeFamilyMemberId||'self')}catch(e){}}
function restoreFamilyContext(){try{const v=localStorage.getItem(FAMILY_CONTEXT_KEY);activeFamilyMemberId=v&&v!=='self'?v:null}catch(e){activeFamilyMemberId=null}}
function familyFilter(query,column='family_member_id'){
  return activeFamilyMemberId?query.eq(column,activeFamilyMemberId):query.is(column,null);
}
function adminFamilyFilter(query,column='family_member_id'){
  return activeAdminFamilyMemberId?query.eq(column,activeAdminFamilyMemberId):query.is(column,null);
}
async function getMyFamilyMembers(){
  if(!sb||!session?.user?.id)return [];
  const {data,error}=await sb.from('family_members').select('*').eq('owner_user_id',session.user.id).eq('active',true).order('full_name');
  if(error)throw error;return data||[];
}
async function getActiveCarePerson(){
  if(!activeFamilyMemberId)return {id:null,full_name:profile?.full_name||'Mi Perfil',relationship:'Titular de la Cuenta',isSelf:true};
  const {data,error}=await sb.from('family_members').select('*').eq('id',activeFamilyMemberId).eq('owner_user_id',session.user.id).single();
  if(error){activeFamilyMemberId=null;saveFamilyContext();return {id:null,full_name:profile?.full_name||'Mi Perfil',relationship:'Titular de la Cuenta',isSelf:true};}
  return {...data,isSelf:false};
}
function personName(ownerName,member){return member?.full_name||ownerName||'Paciente'}

function normalizeWhatsappPhone(value){
  const raw=String(value||'').trim();
  let digits=raw.replace(/\D/g,'');
  if(!digits)return '';
  if(digits.length===8)digits='506'+digits;
  if(digits.startsWith('00'))digits=digits.slice(2);
  return digits;
}
function displayWhatsappPhone(value){
  const digits=normalizeWhatsappPhone(value);
  if(!digits)return 'No registrado';
  if(digits.startsWith('506')&&digits.length===11)return `+506 ${digits.slice(3,7)} ${digits.slice(7)}`;
  return `+${digits}`;
}
function whatsappUrl(phone,patientName='Paciente'){
  const digits=normalizeWhatsappPhone(phone);
  if(!digits)return '';
  const firstName=String(patientName||'Paciente').trim().split(/\s+/)[0]||'Paciente';
  const message=`Hola ${firstName}, me comunico con usted desde BernardHealth en relación con su seguimiento médico.`;
  return `https://wa.me/${digits}?text=${encodeURIComponent(message)}`;
}
async function syncWhatsappFromAuthMetadata(activeSession){
  const phone=activeSession?.user?.user_metadata?.whatsapp_phone;
  if(!phone||!sb)return;
  try{await sb.from('profiles').update({whatsapp_phone:normalizeWhatsappPhone(phone)}).eq('id',activeSession.user.id);}catch(e){}
}
let oneSignalInstance=null;
let oneSignalInitPromise=null;
let oneSignalInitError=null;

function setNotificationLoading(message='Iniciando alertas…'){
  const btn=document.querySelector('#notificationsBtn');
  const txt=document.querySelector('#notificationsBtnText');
  if(!btn||!txt)return;
  btn.disabled=true;
  btn.classList.remove('is-active','is-blocked');
  txt.textContent=message;
}

async function initOneSignal(){
  if(oneSignalInitPromise) return oneSignalInitPromise;
  if(!cfg.oneSignalAppId) throw new Error('Falta el App ID de OneSignal.');
  setNotificationLoading();
  oneSignalInitPromise=new Promise((resolve,reject)=>{
    let finished=false;
    const timeout=setTimeout(()=>{
      if(finished)return;
      finished=true;
      const err=new Error('OneSignal no respondió a tiempo.');
      oneSignalInitError=err;
      refreshNotificationButton('error');
      reject(err);
    },25000);

    window.OneSignalDeferred=window.OneSignalDeferred||[];
    window.OneSignalDeferred.push(async function(OneSignal){
      try{
        await OneSignal.init({
          appId: cfg.oneSignalAppId,
          serviceWorkerPath: 'sw.js',
          serviceWorkerParam: {scope:'./'},
          notifyButton: {enable:false},
          allowLocalhostAsSecureOrigin:false
        });
        oneSignalInstance=OneSignal;
        oneSignalInitError=null;
        OneSignal.Notifications.addEventListener('permissionChange', refreshNotificationButton);
        OneSignal.User.PushSubscription.addEventListener('change', refreshNotificationButton);
        await syncOneSignalIdentity();
        refreshNotificationButton();
        if(!finished){finished=true;clearTimeout(timeout);resolve(OneSignal);}
      }catch(err){
        console.error('OneSignal no pudo iniciar:',err);
        oneSignalInitError=err;
        refreshNotificationButton('error');
        if(!finished){finished=true;clearTimeout(timeout);reject(err);}
      }
    });
  });
  return oneSignalInitPromise;
}

async function syncOneSignalIdentity(){
  if(!oneSignalInstance||!session?.user?.id)return;
  try{
    await oneSignalInstance.login(session.user.id);
    if(profile?.role) await oneSignalInstance.User.addTag('role',profile.role);
    const subscriptionId=oneSignalInstance.User?.PushSubscription?.id||null;
    const optedIn=Boolean(oneSignalInstance.User?.PushSubscription?.optedIn);
    if(subscriptionId){
      const {error}=await sb.from('profiles').update({onesignal_subscription_id:subscriptionId,push_enabled:optedIn,push_updated_at:new Date().toISOString()}).eq('id',session.user.id);
      if(error)console.warn('No se pudo guardar la suscripción push:',error);
    }
  }catch(err){console.warn('No se pudo vincular el dispositivo:',err)}
}

async function sendAdminControlPush(title,body,url='/?open=inbox'){
  if(!session?.access_token)return {ok:false,error:'Falta sesión activa'};
  try{
    const response=await fetch('/.netlify/functions/send-admin-control-push',{
      method:'POST',
      headers:{'Content-Type':'application/json','Authorization':`Bearer ${session.access_token}`},
      body:JSON.stringify({title,body,url})
    });
    const details=await response.json().catch(()=>({}));
    if(!response.ok){
      console.warn('No se pudo enviar la notificación de control:',details);
      return {ok:false,error:details.error||'OneSignal rechazó la notificación',details};
    }
    if(details.delivered===false){
      console.warn('Control guardado, pero no había administrador con push activo:',details);
      return {ok:false,error:details.reason||'No hay un dispositivo administrativo con alertas activas',details};
    }
    return {ok:true,details};
  }catch(err){
    console.warn('Error enviando la notificación de control:',err);
    return {ok:false,error:err.message||'Error de conexión'};
  }
}

async function sendPatientPush(userId,title,body,url='/?open=documents'){
  if(!session?.access_token||!userId)return {ok:false,error:'Falta sesión o paciente'};
  try{
    const response=await fetch('/.netlify/functions/send-patient-push',{
      method:'POST',
      headers:{'Content-Type':'application/json','Authorization':`Bearer ${session.access_token}`},
      body:JSON.stringify({userId,title,body,url})
    });
    const details=await response.json().catch(()=>({}));
    if(!response.ok){
      console.warn('No se pudo enviar la notificación push:',details);
      return {ok:false,error:details.error||'OneSignal rechazó la notificación',details};
    }
    if(details.delivered===false){
      console.warn('Documento guardado, pero no había dispositivo push disponible:',details);
      return {ok:false,error:details.reason||'El paciente no tiene un dispositivo con alertas activas',details};
    }
    return {ok:true,details};
  }catch(err){
    console.warn('Error enviando notificación push:',err);
    return {ok:false,error:err.message||'Error de conexión'};
  }
}
function refreshNotificationButton(forced){
  const btn=document.querySelector('#notificationsBtn'),txt=document.querySelector('#notificationsBtnText');
  if(!btn||!txt)return;
  btn.disabled=false;
  btn.classList.remove('is-active','is-blocked');
  if(forced==='error'){
    txt.textContent='Reintentar alertas';
    btn.classList.add('is-blocked');
    return;
  }
  if(!oneSignalInstance){
    if(oneSignalInitError){txt.textContent='Reintentar alertas';btn.classList.add('is-blocked');btn.disabled=false;}
    else{txt.textContent='Iniciando alertas…';btn.disabled=true;}
    return;
  }
  const permission=oneSignalInstance.Notifications.permission;
  const opted=oneSignalInstance.User.PushSubscription.optedIn;
  if(typeof Notification!=='undefined' && Notification.permission==='denied'){
    txt.textContent='Alertas bloqueadas';btn.classList.add('is-blocked');return;
  }
  if(opted || permission===true){txt.textContent='Alertas activas';btn.classList.add('is-active');return}
  txt.textContent='Activar alertas';
}
async function requestPushPermission(){
  try{
    if(!oneSignalInstance){
      oneSignalInitPromise=null;
      await initOneSignal();
    }
    if(typeof Notification!=='undefined' && Notification.permission==='denied'){
      toast('Las notificaciones están bloqueadas. Habilítelas en Ajustes del teléfono para esta app.');
      refreshNotificationButton();
      return;
    }
    await oneSignalInstance.Notifications.requestPermission();
    if(oneSignalInstance.Notifications.permission){
      await oneSignalInstance.User.PushSubscription.optIn();
      await syncOneSignalIdentity();
      refreshNotificationButton();
      toast('Notificaciones activadas correctamente.');
    }else{
      refreshNotificationButton();
      toast('No se concedió el permiso de notificaciones.');
    }
  }catch(err){
    console.error('Error al activar OneSignal:',err);
    oneSignalInitPromise=null;
    refreshNotificationButton('error');
    toast(`No se pudieron iniciar las alertas: ${err?.message||'error desconocido'}`);
  }
}

const $=s=>document.querySelector(s), main=$('#mainContent');
function toast(m){const t=$('#toast');t.textContent=m;t.classList.remove('hidden');setTimeout(()=>t.classList.add('hidden'),3200)}
function esc(v=''){return String(v).replace(/[&<>'"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]))}
function neutralizeBrandText(v=''){return String(v).replace(/campañas?\s+(?:del\s+)?(?:dr\.?|doctor)\s+benard/gi,'Campañas y Promociones').replace(/(?:dr\.?|doctor)\s+benard/gi,'su médico').replace(/beneficios publicados por\s+su médico/gi,'Beneficios Disponibles');}
function fmtDate(v){if(!v)return 'Sin fecha';return new Intl.DateTimeFormat('es-CR',{dateStyle:'medium',timeStyle:v.includes('T')?'short':undefined}).format(new Date(v))}
function money(n){return new Intl.NumberFormat('es-CR',{style:'currency',currency:'CRC',maximumFractionDigits:0}).format(n||0)}
async function loadAppSettings(){
  if(!sb||!session)return appSettings;
  const {data,error}=await sb.from('app_settings').select('*').eq('id',1).maybeSingle();
  if(!error&&data)appSettings={...appSettings,...data};
  return appSettings;
}
function benefitIcon(key){
  const map={chat:ICONS.inbox,agenda:ICONS.appointments,documents:ICONS.documents,benefits:ICONS.benefits,controls:ICONS.controls,home:ICONS.home};
  return map[key]||ICONS.benefits;
}
function costaRicaNow(){
  const parts=new Intl.DateTimeFormat('en-CA',{timeZone:'America/Costa_Rica',year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false}).formatToParts(new Date());
  const v=Object.fromEntries(parts.map(x=>[x.type,x.value]));
  return {date:new Date(`${v.year}-${v.month}-${v.day}T${v.hour}:${v.minute}:${v.second}-06:00`),hour:Number(v.hour)};
}
function greetingByHour(){
  const h=costaRicaNow().hour;
  if(h<12)return 'Buenos días';
  if(h<18)return 'Buenas tardes';
  return 'Buenas noches';
}
function updateLiveDateTime(){
  const el=$('#liveDateTime'); if(!el)return;
  const now=new Date();
  const date=new Intl.DateTimeFormat('es-CR',{timeZone:'America/Costa_Rica',weekday:'long',day:'numeric',month:'long',year:'numeric'}).format(now);
  const time=new Intl.DateTimeFormat('es-CR',{timeZone:'America/Costa_Rica',hour:'numeric',minute:'2-digit',second:'2-digit'}).format(now);
  el.innerHTML=`<strong>${date}</strong><span>${time}</span>`;
}
setInterval(updateLiveDateTime,1000);
const ICONS={
  home:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 11.5 12 4l9 7.5"/><path d="M5.5 10.5V20h13v-9.5"/><path d="M9.5 20v-6h5v6"/></svg>',
  patients:'<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="9" cy="8" r="3"/><path d="M3.5 19c.5-3.5 2.5-5 5.5-5s5 1.5 5.5 5"/><circle cx="17" cy="9" r="2.3"/><path d="M15.5 14.5c2.8-.5 4.7.8 5 3.5"/></svg>',
  agenda:'<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4M17 3v4M3 10h18"/><path d="M8 14h3M13 14h3M8 17h3"/></svg>',
  inbox:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9"/><path d="M10 21h4"/></svg>',
  more:'<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>',
  controls:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12h4l2-5 4 10 2-5h6"/><path d="M20 5v14M17 8h6M17 16h6"/></svg>',
  appointments:'<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4M17 3v4M3 10h18"/><path d="m9 15 2 2 4-4"/></svg>',
  video:'<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="6" width="13" height="12" rx="2"/><path d="m16 10 5-3v10l-5-3z"/></svg>',
  documents:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5M9 13h6M9 17h6"/></svg>',
  benefits:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12v8H4v-8"/><path d="M2 8h20v4H2z"/><path d="M12 8v12"/><path d="M12 8H8.5A2.5 2.5 0 1 1 11 5.5V8Zm0 0h3.5A2.5 2.5 0 1 0 13 5.5V8Z"/></svg>',
  billing:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 3h14v18l-3-2-4 2-4-2-3 2z"/><path d="M8 8h8M8 12h8M8 16h5"/><path d="M15.5 14.5v3M14 16h3"/></svg>',
  back:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m15 18-6-6 6-6"/><path d="M9 12h11"/></svg>',
  refresh:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6v5h-5"/><path d="M18.2 15a7 7 0 1 1-.7-7.7L20 11"/></svg>'
};
function navForRole(){return profile?.role==='admin'?[['home','home','Inicio'],['patients','patients','Pacientes'],['agenda','agenda','Agenda'],['billing','billing','Facturación'],['more','more','Gestión']]:[['home','home','Inicio'],['controls','controls','Controles'],['appointments','appointments','Citas'],['documents','documents','Documentos'],['benefits','benefits','Beneficios']]}
function renderNav(){const nav=$('#bottomNav');nav.innerHTML=navForRole().map(([id,ic,l])=>`<button class="nav-btn ${id===currentView?'active':''}" data-view="${id}"><span class="nav-icon">${ICONS[ic]}</span><span class="nav-label">${l}</span></button>`).join('');nav.querySelectorAll('button').forEach(b=>b.onclick=()=>{if(b.dataset.view===currentView)return;navigateTo(b.dataset.view)})}
async function getProfile(){const {data,error}=await sb.from('profiles').select('*').eq('id',session.user.id).single();if(error)throw error;restoreFamilyContext();return data}
async function loadAuthenticatedApp(activeSession){
  session=activeSession;
  authenticatedUserId=session?.user?.id||null;
  if(!session){ profile=null; showLogin(); return; }
  try{
    await syncWhatsappFromAuthMetadata(activeSession);
    profile=await getProfile();
    restoreAdminContext();
    await loadAppSettings();
    showApp();
    try{ await initOneSignal(); }catch(err){ console.warn(err); }
    await syncOneSignalIdentity();
    refreshNotificationButton();
  }catch(err){
    console.error('No se pudo cargar el perfil:',err);
    await sb.auth.signOut();
    showLogin();
    toast('No se pudo cargar el perfil. Intente ingresar nuevamente.');
  }
}
async function boot(){
  await prepareInactiveInvitation();
  if(!configured){$('#configWarning').classList.remove('hidden');return}
  const verifyCode=new URLSearchParams(location.search).get('verify');
  if(verifyCode){await renderPublicDocumentVerification(verifyCode);return;}
  try{
    const {data,error}=await sb.auth.getSession();
    if(error)throw error;
    await loadAuthenticatedApp(data.session);
  }catch(err){
    console.error('Error al iniciar:',err);
    showLogin();
    toast('No se pudo iniciar la aplicación. Recargue la página.');
  }
  sb.auth.onAuthStateChange((event,s)=>{
    // No redibujar toda la aplicación durante renovaciones automáticas de sesión.
    // Esto evita que desaparezca un archivo seleccionado y que se cierre la ficha del paciente.
    const nextUserId=s?.user?.id||null;
    if(event==='TOKEN_REFRESHED'||event==='INITIAL_SESSION'||event==='USER_UPDATED') {
      session=s||session;
      return;
    }
    if(event==='PASSWORD_RECOVERY'){
      session=s||session;
      showPasswordRecoveryForm();
      return;
    }
    if(event==='SIGNED_OUT'){
      authenticatedUserId=null;
      clearAdminContext();
      activePatientId=null;
      currentView='home';
      setTimeout(()=>loadAuthenticatedApp(null),0);
      return;
    }
    if(event==='SIGNED_IN'){
      session=s||session;
      if(nextUserId&&nextUserId===authenticatedUserId)return;
      authenticatedUserId=nextUserId;
      setTimeout(()=>loadAuthenticatedApp(s),0);
    }
  });
}
function showLogin(){$('#loginView').classList.remove('hidden');$('#appView').classList.add('hidden')}
function showPasswordRecoveryForm(){
  showLogin();
  document.querySelector('.tabs')?.classList.add('hidden');
  $('#authForm')?.classList.add('hidden');
  $('#signupNotice')?.classList.add('hidden');
  $('#passwordRecoveryPanel')?.classList.remove('hidden');
}
function restoreLoginForm(){
  document.querySelector('.tabs')?.classList.remove('hidden');
  $('#authForm')?.classList.remove('hidden');
  $('#signupNotice')?.classList.remove('hidden');
  $('#passwordRecoveryPanel')?.classList.add('hidden');
  setAuth('login');
}

async function renderPublicDocumentVerification(code){
  showLogin();const card=document.querySelector('.login-card');card.innerHTML='<div class="loading-row"><span class="spinner"></span><div><strong>Verificando documento…</strong><span class="muted">Espere un momento.</span></div></div>';
  try{
    const medical=await sb.rpc('verify_medical_document',{p_code:code});
    const mrow=Array.isArray(medical.data)?medical.data[0]:medical.data;
    if(mrow){card.innerHTML=`<img src="assets/doctor-letterhead-logo.png" class="verify-logo" alt="Doctor Benard Consulta Médica"><h2>Documento Verificado</h2><div class="verify-card"><strong>${esc(mrow.title||'Documento Médico')}</strong><p>${esc(mrow.patient_name||'Paciente')}</p><p class="muted">Emitido: ${fmtDate(mrow.created_at)}</p><span class="pill green">${mrow.status==='active'?'Vigente':'Anulado'}</span><small>Código: ${esc(mrow.document_code)}</small></div>`;return;}
    const receipt=await sb.rpc('verify_payment_receipt',{p_code:code});
    if(receipt.error)throw receipt.error;
    const row=Array.isArray(receipt.data)?receipt.data[0]:receipt.data;
    if(!row){card.innerHTML='<h2>Documento no encontrado</h2><p class="muted">El código no corresponde a un documento vigente.</p>';return;}
    card.innerHTML=`<img src="assets/doctor-letterhead-logo.png" class="verify-logo" alt="Doctor Benard Consulta Médica"><h2>Comprobante Verificado</h2><div class="verify-card"><strong>${esc(row.receipt_number)}</strong><p>${esc(row.patient_name)}</p><p>${esc(row.service_name)}</p><p class="muted">Emitido: ${fmtDate(row.service_date)} · Total: ${crc(row.total_amount)}</p><span class="pill green">${row.status==='active'?'Vigente':'Anulado'}</span><small>Código: ${esc(row.receipt_code)}</small></div>`;
  }catch(err){card.innerHTML=`<h2>No se pudo verificar</h2><p class="muted">${esc(err.message)}</p>`;}
}


async function prepareInactiveInvitation(){
  const token=new URLSearchParams(location.search).get('activate');
  if(!token||!configured)return;
  try{
    const {data,error}=await sb.rpc('get_inactive_patient_invitation',{p_token:token});
    if(error)throw error;
    const row=Array.isArray(data)?data[0]:data;
    if(!row||row.status==='activated')return;
    inactiveInvitationToken=token;
    setAuth('signup');
    if($('#fullName'))$('#fullName').value=row.full_name||'';
    if($('#whatsappPhone'))$('#whatsappPhone').value=displayWhatsappPhone(row.whatsapp_phone).replace('+506 ','');
    const notice=$('#signupNotice');
    if(notice){notice.className='activation-invite-banner';notice.innerHTML=`<strong>Su perfil preliminar está listo</strong><span>Complete correo, identificación y contraseña para activar BernardHealth. Sus documentos preparados se vincularán automáticamente.</span>`;}
    document.querySelector('.login-card')?.classList.add('invitation-mode');
  }catch(err){console.warn('Invitación no disponible:',err);}
}
function showApp(){$('#loginView').classList.add('hidden');$('#appView').classList.remove('hidden');$('#roleLabel').innerHTML='';render()}
$('#tabLogin').onclick=()=>setAuth('login');$('#tabSignup').onclick=()=>setAuth('signup');
function setAuth(m){authMode=m;$('#tabLogin').classList.toggle('active',m==='login');$('#tabSignup').classList.toggle('active',m==='signup');document.querySelectorAll('.signup-only').forEach(e=>e.classList.toggle('hidden',m!=='signup'));document.querySelectorAll('.login-only').forEach(e=>e.classList.toggle('hidden',m!=='login'));const phone=$('#whatsappPhone');if(phone)phone.required=m==='signup';const name=$('#fullName');if(name)name.required=m==='signup';const identification=$('#identification');if(identification)identification.required=m==='signup';$('#authSubmit').textContent=m==='login'?'Ingresar':'Crear cuenta'}
$('#authForm').onsubmit=async e=>{
  e.preventDefault();
  if(!configured)return toast('Configure Supabase primero.');
  const email=$('#email').value.trim(),password=$('#password').value,btn=$('#authSubmit');
  if(!email||!password)return toast('Escriba el correo y la contraseña.');
  const original=btn.textContent;
  btn.disabled=true; btn.textContent=authMode==='login'?'Ingresando…':'Creando…';
  try{
    if(authMode==='login'){
      const {data,error}=await sb.auth.signInWithPassword({email,password});
      if(error)throw error;
      if(!data.session)throw new Error('No se pudo crear la sesión.');
      await loadAuthenticatedApp(data.session);
    }else{
      const full_name=$('#fullName').value.trim();
      const identification=($('#identification')?.value||'').trim();
      const whatsapp_phone=normalizeWhatsappPhone($('#whatsappPhone').value);
      if(!full_name)return toast('Escriba su nombre completo.');
      if(!identification)return toast('Escriba la identificación.');
      if(whatsapp_phone.length<10)return toast('Escriba un número de WhatsApp válido con código de país.');
      const {data,error}=await sb.auth.signUp({email,password,options:{data:{full_name,whatsapp_phone,identification,inactive_invitation_token:inactiveInvitationToken||null}}});
      if(error)throw error;
      if(data.session) await loadAuthenticatedApp(data.session);
      else toast('Cuenta creada. Revise su correo para confirmar el acceso.');
    }
  }catch(err){
    console.error('Error de acceso:',err);
    const msg=err.message==='Invalid login credentials'?'Correo o contraseña incorrectos.':err.message;
    toast(msg);
  }finally{
    btn.disabled=false; btn.textContent=original;
  }
};
const forgotPasswordBtn=$('#forgotPasswordBtn');
if(forgotPasswordBtn)forgotPasswordBtn.onclick=async()=>{
  if(!configured)return toast('Configure Supabase primero.');
  const email=($('#email')?.value||'').trim();
  if(!email)return toast('Escriba primero su correo electrónico.');
  forgotPasswordBtn.disabled=true;forgotPasswordBtn.textContent='Enviando enlace…';
  try{
    const redirectTo=`${location.origin}${location.pathname}`;
    const {error}=await sb.auth.resetPasswordForEmail(email,{redirectTo});
    if(error)throw error;
    toast('Enlace enviado. Revise su correo y la carpeta de spam.');
  }catch(err){toast(err.message||'No se pudo enviar el enlace.');}
  finally{forgotPasswordBtn.disabled=false;forgotPasswordBtn.textContent='¿Olvidó su contraseña?';}
};
const saveNewPasswordBtn=$('#saveNewPasswordBtn');
if(saveNewPasswordBtn)saveNewPasswordBtn.onclick=async()=>{
  const password=$('#newPassword').value,confirmPassword=$('#confirmNewPassword').value;
  if(password.length<6)return toast('La contraseña debe tener al menos 6 caracteres.');
  if(password!==confirmPassword)return toast('Las contraseñas no coinciden.');
  saveNewPasswordBtn.disabled=true;saveNewPasswordBtn.textContent='Guardando…';
  try{
    const {error}=await sb.auth.updateUser({password});
    if(error)throw error;
    toast('Contraseña actualizada correctamente.');
    await sb.auth.signOut();
    restoreLoginForm();
  }catch(err){toast(err.message||'No se pudo cambiar la contraseña.');}
  finally{saveNewPasswordBtn.disabled=false;saveNewPasswordBtn.textContent='Guardar nueva contraseña';}
};

const notificationsButton=$('#notificationsBtn');
if(notificationsButton) notificationsButton.addEventListener('click', requestPushPermission);
const logoutButton=$('#logoutBtn');
if(logoutButton) logoutButton.addEventListener('click', async()=>{try{if(oneSignalInstance)await oneSignalInstance.logout()}catch(e){} await sb.auth.signOut();});
function injectPageToolbar(){
  if(currentView==='home') return;
  const existing=main.querySelector('.page-toolbar');
  if(existing) existing.remove();
  const bar=document.createElement('div');
  bar.className='page-toolbar';
  bar.innerHTML=`<button type="button" class="page-tool-btn" id="pageBackBtn" aria-label="Volver"><span>${ICONS.back}</span><b>Volver</b></button><button type="button" class="page-tool-btn" id="pageRefreshBtn" aria-label="Actualizar"><span>${ICONS.refresh}</span><b>Actualizar</b></button>`;
  main.prepend(bar);
  $('#pageBackBtn').onclick=()=>navigateBack();
  $('#pageRefreshBtn').onclick=async()=>{
    const btn=$('#pageRefreshBtn');
    btn.disabled=true;
    btn.classList.add('is-loading');
    try{ await render(); toast('Información actualizada.'); }
    catch(e){ toast('No se pudo actualizar.'); }
  };
}
async function render(){renderNav();main.innerHTML='<div class="empty">Cargando…</div>';try{if(profile.role==='admin')await renderAdmin();else await renderPatient();injectPageToolbar();if(pendingRestoreScrollY!==null){const y=pendingRestoreScrollY;pendingRestoreScrollY=null;requestAnimationFrame(()=>requestAnimationFrame(()=>window.scrollTo({top:y,left:0,behavior:'auto'})));}}catch(e){main.innerHTML=`<div class="alert-banner"><strong>No se pudo cargar.</strong><br>${esc(e.message)}</div>`;injectPageToolbar()}}
async function renderPatient(){const routes={home:patientHome,controls:patientControls,appointments:patientAppointments,documents:patientDocuments,benefits:patientBenefits,alerts:patientAlerts,family:patientFamily};await (routes[currentView]||patientHome)();await injectFamilyContextBar();}

async function injectFamilyContextBar(){
  if(profile?.role==='admin'||currentView==='family')return;
  const members=await getMyFamilyMembers();
  if(!members.length)return;
  const person=await getActiveCarePerson();
  const bar=document.createElement('section');bar.className='family-context-bar';
  bar.innerHTML=`<div><small>PERFIL EN USO</small><strong>${esc(person.full_name)}</strong><span>${esc(person.relationship||'Familiar')}</span></div><button class="btn secondary" data-go="family">Cambiar Perfil</button>`;
  main.prepend(bar);
  bar.querySelector('button').onclick=()=>navigateTo('family');
}
function parseFamilyBirthDate(value){
  const raw=String(value||'').trim();
  if(!raw)return null;
  const match=raw.match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{4})$/);
  if(!match)return false;
  const day=Number(match[1]),month=Number(match[2]),year=Number(match[3]);
  const currentYear=new Date().getFullYear();
  if(year<1900||year>currentYear||month<1||month>12||day<1||day>31)return false;
  const date=new Date(Date.UTC(year,month-1,day));
  if(date.getUTCFullYear()!==year||date.getUTCMonth()!==month-1||date.getUTCDate()!==day)return false;
  return `${year}-${String(month).padStart(2,'0')}-${String(day).padStart(2,'0')}`;
}

async function patientFamily(){
  const members=await getMyFamilyMembers();
  const selfActive=!activeFamilyMemberId;
  const cards=[`<article class="family-member-card ${selfActive?'active':''}"><div class="family-avatar">${ICONS.patients}</div><div><strong>${esc(profile.full_name||'Mi Perfil')}</strong><span>Titular de la Cuenta</span><small>Usa su correo y WhatsApp registrados.</small></div><button class="btn ${selfActive?'secondary':'primary'}" data-family-select="self">${selfActive?'Perfil Activo':'Usar Perfil'}</button></article>`]
    .concat(members.map(m=>`<article class="family-member-card ${activeFamilyMemberId===m.id?'active':''}"><div class="family-avatar">${ICONS.patients}</div><div><strong>${esc(m.full_name)}</strong><span>${esc(m.relationship||'Familiar')}</span><small>${m.birth_date?`Nacimiento: ${fmtDate(m.birth_date)}`:'Perfil gestionado desde esta cuenta'}</small></div><div class="family-card-actions"><button class="btn ${activeFamilyMemberId===m.id?'secondary':'primary'}" data-family-select="${m.id}">${activeFamilyMemberId===m.id?'Perfil Activo':'Usar Perfil'}</button><button class="btn danger-soft" data-family-delete="${m.id}" data-family-name="${esc(m.full_name)}">Eliminar</button></div></article>`));
  main.innerHTML=`<section class="family-hero"><div><span class="eyebrow">CUENTA FAMILIAR</span><h2>Mi Familia</h2><p>Agregue hijos, cónyuge, adultos mayores u otras personas a su cargo. Esta opción es completamente voluntaria; si usa la app solo para usted, continúe con su perfil individual.</p></div><span>${ICONS.patients}</span></section><div class="family-members-grid">${cards.join('')}</div><details class="family-add-panel"><summary><span><strong>Agregar a Mi Familia</strong><small>No necesita otro correo ni número de teléfono.</small></span><b>⌄</b></summary><form id="familyMemberForm" class="family-add-body"><div class="form-grid"><div class="field"><label>Nombre Completo</label><input id="familyFullName" required></div><div class="field"><label>Relación</label><select id="familyRelationship"><option>Hijo</option><option>Hija</option><option>Esposo</option><option>Esposa</option><option>Madre</option><option>Padre</option><option>Adulto Mayor a Cargo</option><option>Residente del Hogar</option><option>Otro Familiar</option><option>Otra Persona a Cargo</option></select></div><div class="field"><label>Fecha de Nacimiento</label><input id="familyBirthDate" type="text" inputmode="numeric" maxlength="10" placeholder="DD/MM/AAAA"><small class="field-help">Puede escribirla manualmente. Ejemplo: 15/08/1942</small></div><div class="field"><label>Sexo</label><select id="familySex"><option value="">No Indicado</option><option>Femenino</option><option>Masculino</option><option>Otro</option></select></div><div class="field field-full"><label>Identificación</label><input id="familyIdentification" placeholder="Opcional"></div></div><button class="btn primary full" type="submit">Guardar Familiar</button></form></details>`;
  main.querySelectorAll('[data-family-select]').forEach(b=>b.onclick=async()=>{activeFamilyMemberId=b.dataset.familySelect==='self'?null:b.dataset.familySelect;saveFamilyContext();currentView='home';await render();});
  main.querySelectorAll('[data-family-delete]').forEach(b=>b.onclick=async()=>{if(!confirm(`¿Eliminar el perfil de ${b.dataset.familyName}? Los registros clínicos asociados no se eliminarán automáticamente.`))return;const {error}=await sb.from('family_members').update({active:false}).eq('id',b.dataset.familyDelete).eq('owner_user_id',session.user.id);if(error)return toast(error.message);if(activeFamilyMemberId===b.dataset.familyDelete){activeFamilyMemberId=null;saveFamilyContext()}toast('Perfil familiar retirado.');render();});
  const familyBirthInput=$('#familyBirthDate');
  if(familyBirthInput){
    familyBirthInput.addEventListener('input',e=>{
      const digits=e.target.value.replace(/\D/g,'').slice(0,8);
      let formatted=digits;
      if(digits.length>2)formatted=`${digits.slice(0,2)}/${digits.slice(2)}`;
      if(digits.length>4)formatted=`${digits.slice(0,2)}/${digits.slice(2,4)}/${digits.slice(4)}`;
      e.target.value=formatted;
    });
    familyBirthInput.addEventListener('blur',e=>{
      const parsed=parseFamilyBirthDate(e.target.value);
      if(parsed&&parsed!==false){
        const [year,month,day]=parsed.split('-');
        e.target.value=`${day}/${month}/${year}`;
      }
    });
  }
  $('#familyMemberForm').onsubmit=async e=>{e.preventDefault();const birthDate=parseFamilyBirthDate($('#familyBirthDate').value);if(birthDate===false)return toast('Escriba una fecha válida como DD/MM/AAAA.');const payload={owner_user_id:session.user.id,full_name:$('#familyFullName').value.trim(),relationship:$('#familyRelationship').value,birth_date:birthDate,sex:$('#familySex').value||null,identification:$('#familyIdentification').value.trim()||null};if(!payload.full_name)return toast('Escriba el nombre completo.');const {data,error}=await sb.from('family_members').insert(payload).select().single();if(error)return toast(error.message);activeFamilyMemberId=data.id;saveFamilyContext();toast('Familiar agregado correctamente.');render();};
}
async function patientHome(){
  const uid=session.user.id,now=new Date().toISOString();
  const [membershipsQ,appointmentsQ,notificationsQ,couponsQ,campaignsQ,adsQ,benefitsQ,redemptionsQ]=await Promise.all([
    sb.from('memberships').select('*').eq('patient_id',uid).maybeSingle(),
    familyFilter(sb.from('appointments').select('*').eq('patient_id',uid)).gte('starts_at',now).in('status',['requested','confirmed']).order('starts_at').limit(1),
    sb.from('notifications').select('*').eq('user_id',uid).eq('read',false).order('created_at',{ascending:false}).limit(5),
    sb.from('coupons').select('*').eq('active',true).gte('expires_at',now).order('created_at',{ascending:false}),
    sb.from('campaigns').select('*').eq('active',true).gte('ends_at',now).order('created_at',{ascending:false}),
    sb.from('advertisements').select('*').eq('active',true).gte('ends_at',now).order('created_at',{ascending:false}),
    sb.from('membership_benefits').select('*',{count:'exact',head:true}).eq('active',true),
    sb.from('coupon_redemptions').select('coupon_id,status').eq('patient_id',uid)
  ]);
  const m=membershipsQ.data,a=appointmentsQ.data,n=notificationsQ.data||[],redemptions=redemptionsQ.data||[];
  promotionRedemptionCache=preferredRedemptionMap(redemptions);
  const usedCoupons=new Set([...promotionRedemptionCache.values()].filter(r=>r.status==='used').map(r=>r.coupon_id));
  const featuredCoupons=(couponsQ.data||[]).filter(x=>x.featured===true&&!usedCoupons.has(x.id)&&x.image_url);
  const featuredCampaigns=(campaignsQ.data||[]).filter(x=>x.featured===true&&x.image_url);
  const featuredAds=(adsQ.data||[]).filter(x=>x.featured===true&&x.image_url);
  const promotions=[...featuredCoupons.map(x=>({...x,_type:'coupon'})),...featuredCampaigns.map(x=>({...x,_type:'campaign'})),...featuredAds.map(x=>({...x,_type:'advertisement'}))];
  promotionDetailCache=new Map(promotions.map(x=>[`${x._type}:${x.id}`,x]));
  const verified=m?.status==='active';
  const availableBenefits=(couponsQ.data?.length||0)+(campaignsQ.data?.length||0)+(adsQ.data?.length||0)+(benefitsQ.count||0);
  const promoMarkup=promotions.length?`<section class="featured-promotions"><div class="featured-promotions-head"><div><span class="eyebrow">PARA USTED</span><h2>Beneficios y campañas</h2></div><small>Deslice para ver más</small></div><div class="promotion-carousel" id="promotionCarousel">${promotions.map((x,i)=>`<button class="promotion-slide" type="button" data-promo-type="${x._type}" data-promo-id="${x.id}" aria-label="Abrir ${esc(x.title)}"><img src="${esc(x.image_url)}" alt="" loading="${i?'lazy':'eager'}"></button>`).join('')}</div><div class="promotion-dots">${promotions.map((_,i)=>`<button type="button" data-promo-dot="${i}" class="${i===0?'active':''}" aria-label="Ir a promoción ${i+1}"></button>`).join('')}</div></section>`:'';
  main.innerHTML=`
    <section class="welcome-row"><div class="welcome-copy"><span class="eyebrow">BERNARDHEALTH</span><h2>¡Bienvenido!</h2><p>Gestione sus citas, documentos, controles y beneficios desde un solo lugar.</p></div><div class="welcome-logo"><img src="assets/logo.png" alt="BernardHealth"></div></section>
    <section class="membership-hero ${verified?'is-active':'is-inactive'}"><div class="membership-icon">${ICONS.benefits}</div><div class="membership-copy"><span class="status-chip">${verified?'Paciente verificado':'Acceso pendiente'}</span><h2>Servicios y Beneficios</h2><p>${verified?'Su espacio de atención y seguimiento está disponible.':'Su médico debe verificar su cuenta para habilitar el acceso completo.'}</p></div><button class="hero-action" data-go="benefits">Ver beneficios <span>›</span></button></section>
    ${promoMarkup}
    <div class="dashboard-metrics three"><article class="metric-card metric-link" data-go="appointments" role="button" tabindex="0" aria-label="Ir a citas"><span class="metric-icon mint">${ICONS.appointments}</span><div><small>Próxima cita</small><strong class="compact">${a?.[0]?fmtDate(a[0].starts_at):'Sin cita'}</strong><p>${esc(a?.[0]?.type||'Puede solicitarla desde la app')}</p></div></article><article class="metric-card metric-link" data-go="alerts" role="button" tabindex="0" aria-label="Ir a alertas"><span class="metric-icon lilac">${ICONS.inbox}</span><div><small>Alertas nuevas</small><strong>${n.length}</strong><p>${n.length?'Pendientes de revisar':'Todo está al día'}</p></div></article><article class="metric-card metric-link" data-go="benefits" role="button" tabindex="0" aria-label="Ir a beneficios"><span class="metric-icon rose">${ICONS.benefits}</span><div><small>Beneficios Disponibles</small><strong>${availableBenefits}</strong><p>Cupones, campañas y servicios</p></div></article></div>
    ${n.length?`<div class="section-title"><h2>Alertas Recientes</h2></div><div class="list notification-list">${n.map(x=>`<div class="item"><span class="list-icon">${ICONS.inbox}</span><div class="item-main"><strong>${esc(x.title)}</strong><span class="muted">${esc(x.body)}</span></div><button class="btn ghost" data-action="mark-read" data-id="${x.id}">Marcar vista</button></div>`).join('')}</div>`:''}
    <details class="primary-action-menu"><summary><span class="primary-action-icon">${ICONS.more}</span><span><strong>¿Qué Desea Hacer?</strong><small>Abra este menú para elegir una acción</small></span><b>⌄</b></summary><div class="primary-action-options"><button data-go="appointments"><span class="quick-icon lilac">${ICONS.appointments}</span><span><strong>Agendar una Cita</strong><small>Presencial, virtual o domiciliaria</small></span><b>›</b></button><button data-go="documents"><span class="quick-icon mint">${ICONS.documents}</span><span><strong>ENVIAR EXÁMENES</strong><small>PDF, Fotografías o Resultados</small></span><b>›</b></button><button data-go="controls"><span class="quick-icon rose">${ICONS.controls}</span><span><strong>Registrar un control</strong><small>Signos vitales y mediciones</small></span><b>›</b></button><button data-go="benefits"><span class="quick-icon amber">${ICONS.benefits}</span><span><strong>Ver beneficios</strong><small>Cupones, campañas y promociones</small></span><b>›</b></button><button data-go="family"><span class="quick-icon lilac">${ICONS.patients}</span><span><strong>Mi Familia</strong><small>Gestione familiares o personas a su cargo</small></span><b>›</b></button></div></details>
    <section class="whatsapp-contact-card" aria-label="Comunicación directa con su médico"><div class="whatsapp-contact-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3a8.8 8.8 0 0 0-7.5 13.4L3 21l4.8-1.4A8.8 8.8 0 1 0 12 3Z"/><path d="M8.7 8.2c.2-.4.4-.4.7-.4h.5c.2 0 .4.1.5.5l.7 1.7c.1.3.1.5-.1.7l-.6.7c-.2.2-.1.4 0 .6.5 1 1.4 1.8 2.4 2.4.2.1.4.2.6 0l.8-.9c.2-.2.4-.3.7-.1l1.8.8c.3.1.5.3.5.5 0 .3-.1 1.3-.7 1.8-.5.5-1.3.8-2.2.6-1.1-.2-2.6-.8-4.3-2.3-1.4-1.3-2.4-2.8-2.7-3.9-.3-1-.1-1.8.3-2.3Z"/></svg></div><div class="whatsapp-contact-copy"><span>COMUNICACIÓN DIRECTA</span><h2>¿Tiene alguna duda o consulta?</h2><p>Escríbale directamente a su médico por WhatsApp.</p></div><button type="button" class="whatsapp-contact-button" data-action="open-doctor-whatsapp">Abrir WhatsApp</button></section>`;
  setupPromotionCarousel();
}

async function patientControls(){
  const {data,error}=await familyFilter(sb.from('health_records').select('*').eq('patient_id',session.user.id).is('patient_hidden_at',null)).order('recorded_at',{ascending:false}).limit(30);
  if(error)throw error;
  const rows=data||[];
  main.innerHTML=`<section class="controls-hero"><div><span class="eyebrow">SEGUIMIENTO DE SALUD</span><h2>Registrar mis controles</h2><p>Complete únicamente los datos que tenga disponibles. Todos se guardarán juntos en un solo registro.</p></div><span>${ICONS.controls}</span></section><div class="card unified-controls-card"><form id="controlForm" class="patient-control-form unified-control-form"><input id="cEditId" type="hidden"><div class="control-section-title"><strong id="controlFormTitle">Signos vitales y mediciones</strong><small>Puede dejar campos vacíos.</small></div><div class="form-grid control-vitals-grid"><div class="field"><label>Presión arterial</label><div class="pressure-pair"><input id="cSystolic" type="number" inputmode="numeric" min="40" max="300" placeholder="Sistólica"><span>/</span><input id="cDiastolic" type="number" inputmode="numeric" min="20" max="200" placeholder="Diastólica"></div><small class="field-help">Ejemplo: 120 / 80 mmHg</small></div><div class="field"><label>Glucosa</label><input id="cGlucose" type="number" inputmode="decimal" min="20" max="700" step="1" placeholder="mg/dL"></div><div class="field"><label>Peso</label><input id="cWeight" type="number" inputmode="decimal" min="1" max="500" step="0.1" placeholder="kg"></div><div class="field"><label>Saturación de oxígeno</label><input id="cOxygen" type="number" inputmode="numeric" min="50" max="100" step="1" placeholder="%"></div><div class="field"><label>Frecuencia cardiaca</label><input id="cHeartRate" type="number" inputmode="numeric" min="20" max="250" step="1" placeholder="latidos por minuto"></div><div class="field"><label>Temperatura</label><input id="cTemperature" type="number" inputmode="decimal" min="30" max="45" step="0.1" placeholder="°C"></div><div class="field"><label>Frecuencia respiratoria</label><input id="cRespiratoryRate" type="number" inputmode="numeric" min="5" max="80" step="1" placeholder="respiraciones por minuto"></div><div class="field field-full"><label>Síntomas u observaciones</label><textarea id="cNotes" placeholder="Ejemplo: mareo, dolor, falta de aire o cualquier detalle importante"></textarea></div></div><div class="item-actions control-form-actions"><button class="btn primary" type="submit" id="saveControlBtn">Guardar control completo</button><button class="btn secondary hidden" type="button" id="cancelControlEditBtn">Cancelar edición</button></div></form></div><div class="section-title"><h2>Historial</h2></div><div class="list">${rows.length?rows.map(r=>`<div class="item control-history-item"><div class="item-main"><strong>${esc(labelType(r.record_type))}</strong><span class="control-history-values">${esc(r.value_text)}</span><span class="muted">${fmtDate(r.recorded_at)}${r.notes?` · ${esc(r.notes)}`:''}</span></div><div class="item-actions"><button class="btn secondary" data-action="edit-patient-control" data-id="${r.id}">Editar</button><button class="btn danger" data-action="hide-patient-control" data-id="${r.id}">Eliminar de mi historial</button></div></div>`).join(''):'<div class="empty">Aún no hay controles.</div>'}</div>`;
  const parseValues=value=>{const t=String(value||'');return {systolic:(t.match(/Presión arterial:\s*(\d+)\/(\d+)/i)||[])[1]||'',diastolic:(t.match(/Presión arterial:\s*(\d+)\/(\d+)/i)||[])[2]||'',glucose:(t.match(/Glucosa:\s*([\d.]+)/i)||[])[1]||'',weight:(t.match(/Peso:\s*([\d.]+)/i)||[])[1]||'',oxygen:(t.match(/Saturación de oxígeno:\s*([\d.]+)/i)||[])[1]||'',heartRate:(t.match(/Frecuencia cardiaca:\s*([\d.]+)/i)||[])[1]||'',temperature:(t.match(/Temperatura:\s*([\d.]+)/i)||[])[1]||'',respiratoryRate:(t.match(/Frecuencia respiratoria:\s*([\d.]+)/i)||[])[1]||''}};
  window.editPatientControl=async id=>{const row=rows.find(x=>x.id===id);if(!row)return toast('No se encontró el control.');const v=parseValues(row.value_text);$('#cEditId').value=row.id;$('#cSystolic').value=v.systolic;$('#cDiastolic').value=v.diastolic;$('#cGlucose').value=v.glucose;$('#cWeight').value=v.weight;$('#cOxygen').value=v.oxygen;$('#cHeartRate').value=v.heartRate;$('#cTemperature').value=v.temperature;$('#cRespiratoryRate').value=v.respiratoryRate;$('#cNotes').value=row.notes||'';$('#controlFormTitle').textContent='Editar control';$('#saveControlBtn').textContent='Guardar cambios';$('#cancelControlEditBtn').classList.remove('hidden');document.querySelector('.unified-controls-card')?.scrollIntoView({behavior:'smooth',block:'start'});};
  window.hidePatientControl=async id=>{if(!confirm('¿Quitar este control de su historial? El médico podrá conservarlo en su panel.'))return;const {error}=await sb.rpc('patient_hide_health_record',{p_record_id:id});if(error)return toast(error.message);toast('Control retirado de su historial.');render();};
  $('#cancelControlEditBtn').onclick=()=>render();
  $('#controlForm').onsubmit=async e=>{e.preventDefault();const values=[];const systolic=$('#cSystolic').value.trim(),diastolic=$('#cDiastolic').value.trim();if(systolic||diastolic){if(!systolic||!diastolic)return toast('Complete ambos valores de la presión arterial.');values.push(`Presión arterial: ${systolic}/${diastolic} mmHg`)}const glucose=$('#cGlucose').value.trim();if(glucose)values.push(`Glucosa: ${glucose} mg/dL`);const weight=$('#cWeight').value.trim();if(weight)values.push(`Peso: ${weight} kg`);const oxygen=$('#cOxygen').value.trim();if(oxygen)values.push(`Saturación de oxígeno: ${oxygen}%`);const heartRate=$('#cHeartRate').value.trim();if(heartRate)values.push(`Frecuencia cardiaca: ${heartRate} lpm`);const temperature=$('#cTemperature').value.trim();if(temperature)values.push(`Temperatura: ${temperature} °C`);const respiratoryRate=$('#cRespiratoryRate').value.trim();if(respiratoryRate)values.push(`Frecuencia respiratoria: ${respiratoryRate} rpm`);const notes=$('#cNotes').value.trim();if(!values.length&&!notes)return toast('Complete al menos un control o una observación.');const valueText=values.length?values.join(' · '):'Sin mediciones numéricas';const editId=$('#cEditId').value;if(editId){const {error}=await sb.rpc('patient_update_health_record',{p_record_id:editId,p_value_text:valueText,p_notes:notes});if(error)return toast(error.message);toast('Control actualizado correctamente.');return render();}const {error}=await sb.from('health_records').insert({patient_id:session.user.id,family_member_id:activeFamilyMemberId,record_type:'vital_signs',value_text:valueText,notes});if(error)return toast(error.message);const patientName=activeFamilyMember?.full_name||profile?.full_name||'Un paciente';const push=await sendAdminControlPush('Nuevo control registrado',`${patientName} registró: ${valueText}${notes?` · ${notes}`:''}`,'/?open=inbox');toast(push.ok?'Control registrado y notificación enviada.':`Control registrado. Aviso push pendiente: ${push.error}`);render()};
}

function labelType(t){return({pressure:'Presión Arterial',glucose:'Glucosa',weight:'Peso',symptom:'Síntoma',vital_signs:'Control completo'})[t]||'Control de salud'}
function statusLabel(status){
  return ({requested:'Pendiente de confirmación',confirmed:'Confirmada',rejected:'Rechazada',cancelled:'Cancelada',completed:'Atendida',no_show:'No asistió'})[status]||'Pendiente';
}
function membershipLabel(status){
  return ({active:'Verificado',inactive:'Pendiente',suspended:'Suspendido',pending:'Pendiente'})[status]||'Sin verificar';
}
function documentTypeLabel(type){
  return ({lab_result:'Resultado de Laboratorio',study:'Estudio o Imagen',image_order:'Orden de estudios de imagen',other:'Otro Documento',prescription:'Receta médica',lab_order:'Orden de laboratorio',instructions:'Indicaciones médicas',consultation_summary:'Resumen de consulta',results_report:'Reporte de resultados',payment_receipt:'Comprobante de pago'})[type]||'Documento médico';
}
function virtualContactTypeLabel(type){return ({treatment_question:'Duda sobre tratamiento',follow_up:'Seguimiento de evolución',results_question:'Consulta sobre resultados',control_review:'Revisión de controles',other:'Otro motivo breve'})[type]||'Cita virtual';}
function virtualContactStatusLabel(status){return ({requested:'Pendiente',approved:'Aprobado',scheduled:'Programado',used:'Atendido',rejected:'Rechazado',cancelled:'Cancelado'})[status]||'Pendiente';}
function couponStatusLabel(status){return ({requested:'Solicitado',approved:'Aprobado',used:'Utilizado',rejected:'Rechazado',cancelled:'Cancelado'})[status]||'Solicitado';}
function redemptionRank(status){return ({used:4,approved:3,requested:2,rejected:1,cancelled:0})[status]??0;}
function preferredRedemptionMap(rows=[]){const map=new Map();for(const row of rows){const current=map.get(row.coupon_id);if(!current||redemptionRank(row.status)>redemptionRank(current.status))map.set(row.coupon_id,row);}return map;}
function dayLabel(day){return ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'][Number(day)]||''}
function localDateISO(d=new Date()){
  const parts=new Intl.DateTimeFormat('en-CA',{timeZone:'America/Costa_Rica',year:'numeric',month:'2-digit',day:'2-digit'}).formatToParts(d);
  const v=Object.fromEntries(parts.map(x=>[x.type,x.value])); return `${v.year}-${v.month}-${v.day}`;
}
function formatSlotTime(v){return new Intl.DateTimeFormat('es-CR',{timeZone:'America/Costa_Rica',hour:'numeric',minute:'2-digit'}).format(new Date(v))}

async function patientAlerts(){
  const {data:alerts,error}=await sb.from('notifications').select('*').eq('user_id',session.user.id).order('created_at',{ascending:false}).limit(100);
  if(error)throw error;
  const pendingAlerts=(alerts||[]).filter(x=>!x.read);
  const viewedAlerts=(alerts||[]).filter(x=>x.read);
  const pendingMarkup=pendingAlerts.length?pendingAlerts.map(x=>`<div class="item"><span class="list-icon lilac">${ICONS.inbox}</span><div class="item-main"><strong>${esc(x.title)}</strong><span class="muted">${esc(x.body)} · ${fmtDate(x.created_at)}</span></div><button class="btn ghost" data-action="mark-read" data-id="${x.id}">Ver</button></div>`).join(''):'<div class="empty polished alert-all-clear"><strong>Está al día</strong><span>No tiene alertas pendientes.</span></div>';
  const historyMarkup=viewedAlerts.length?viewedAlerts.map(x=>`<div class="item compact-item"><span class="list-icon">${ICONS.inbox}</span><div class="item-main"><strong>${esc(x.title)}</strong><span class="muted">${esc(x.body)} · ${fmtDate(x.created_at)}</span></div><span class="pill green">Vista</span></div>`).join(''):'<div class="empty">Todavía no hay alertas vistas.</div>';
  main.innerHTML=`<section class="hero"><h2>Mis Alertas</h2><p>Revise avisos sobre citas, documentos y seguimiento médico.</p></section><div class="section-title counter-heading"><h2>Pendientes</h2><span class="counter-badge"><strong>${pendingAlerts.length}</strong><small>pendiente${pendingAlerts.length===1?'':'s'}</small></span></div><div class="list notification-list">${pendingMarkup}</div><details class="archive-panel patient-alert-history"><summary>Historial de alertas vistas <span>${viewedAlerts.length}</span></summary><div class="list archive-list">${historyMarkup}</div></details>`;
}


async function patientAppointments(){
  const {data:history,error:hErr}=await familyFilter(sb.from('appointments').select('*').eq('patient_id',session.user.id)).order('starts_at',{ascending:false});
  if(hErr) throw hErr;
  const today=localDateISO();
  const nowTs=Date.now();
  const upcoming=(history||[]).filter(a=>new Date(a.starts_at).getTime()>=nowTs&&['requested','confirmed'].includes(a.status));
  const past=(history||[]).filter(a=>!upcoming.some(x=>x.id===a.id));
  const upcomingMarkup=upcoming.length?upcoming.map(a=>`<article class="patient-appointment-premium"><div class="appointment-date-badge"><strong>${new Date(a.starts_at).toLocaleDateString('es-CR',{day:'2-digit'})}</strong><span>${new Date(a.starts_at).toLocaleDateString('es-CR',{month:'short'}).replace('.','')}</span></div><div class="appointment-premium-main"><div class="appointment-premium-heading"><div><span class="appointment-type-label">${esc(a.type)}</span><h3>${formatSlotTime(a.starts_at)}</h3></div><span class="pill ${a.status==='confirmed'?'green':'amber'}">${esc(statusLabel(a.status))}</span></div><p class="appointment-reason">${esc(a.reason||'Sin Motivo Registrado')}</p><details class="cancel-appointment-details"><summary><span class="cancel-circle-icon" aria-hidden="true">⦸</span><span>Cancelar Cita</span><b>⌄</b></summary><div id="cancelBox-${a.id}" class="cancel-appointment-box"><div class="field"><label>Motivo de Cancelación</label><select id="cancelReason-${a.id}"><option value="">Seleccione una Opción</option><option>No Podré Asistir</option><option>Problema de Transporte</option><option>Me Siento Mejor</option><option>Prefiero Reprogramar</option><option>Emergencia Personal o Familiar</option><option>Otro Motivo</option></select></div><div class="field"><label>Comentario Opcional</label><textarea id="cancelNote-${a.id}" placeholder="Puede Agregar un Detalle"></textarea></div><div class="cancel-actions"><button class="btn danger" data-action="cancel-patient-appointment" data-id="${a.id}">Confirmar Cancelación</button></div></div></details></div></article>`).join(''):'<div class="empty polished appointment-empty-state"><span><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4M17 3v4M3 10h18"/><path d="m9 15 2 2 4-4"/></svg></span><strong>No Tiene Citas Activas</strong><p>Agende su próxima atención en pocos pasos.</p></div>';
  main.innerHTML=`
    <section class="patient-appointments-hero"><div><span class="eyebrow">ATENCIÓN MÉDICA</span><h2>Agendar una Cita</h2><p>Seleccione el tipo de atención, la fecha y el horario que mejor le convenga.</p></div><span class="patient-appointments-hero-icon">${ICONS.appointments}</span></section>
    <div class="appointment-booking-card">
      <div class="booking-step"><span class="booking-step-number">1</span><div><h3>Tipo de Atención</h3><p>Elija cómo desea recibir la consulta.</p></div></div>
      <div class="appointment-type-grid" id="appointmentTypeGrid">
        <button type="button" class="appointment-type-option selected" data-type="Consulta presencial"><span>${ICONS.appointments}</span><strong>Consulta Presencial</strong><small>Atención en consultorio</small></button>
        <button type="button" class="appointment-type-option" data-type="Cita virtual"><span>${ICONS.video}</span><strong>Cita Virtual</strong><small>Atención por videollamada</small></button>
        <button type="button" class="appointment-type-option" data-type="Visita domiciliaria"><span>${ICONS.home}</span><strong>Visita Domiciliaria</strong><small>Atención en su hogar</small></button>
      </div>
      <select id="aType" class="hidden"><option>Consulta presencial</option><option>Cita virtual</option><option>Visita domiciliaria</option></select>
      <div class="booking-divider"></div>
      <div class="booking-step"><span class="booking-step-number">2</span><div><h3>Fecha de la Cita</h3><p>Seleccione el día que desea consultar.</p></div></div>
      <div class="field premium-date-field"><label>Fecha Disponible</label><input id="aDay" type="date" min="${today}" value="${today}"></div>
      <div class="booking-divider"></div>
      <div class="booking-step"><span class="booking-step-number">3</span><div><h3>Horario Disponible</h3><p>Toque el horario que desea reservar.</p></div></div>
      <div id="slotGrid" class="slot-grid premium-slot-grid"><div class="empty">Consultando Horarios Disponibles…</div></div>
      <div class="booking-divider"></div>
      <div class="field"><label>Motivo de la Consulta</label><textarea id="aReason" placeholder="Describa Brevemente el Motivo de la Consulta" required></textarea></div>
      <input id="aSelectedSlot" type="hidden"><input id="aSelectedDuration" type="hidden" value="30">
      <button class="btn primary full premium-booking-submit" type="button" id="requestAppointmentBtn">Solicitar Cita</button>
    </div>
    <div class="section-title major-heading appointments-list-heading counter-heading"><h2>Mis Próximas Citas</h2><span class="counter-badge counter-badge-light"><strong>${upcoming.length}</strong><small>activa${upcoming.length===1?'':'s'}</small></span></div>
    <div class="patient-appointments-list">${upcomingMarkup}</div>
    <details class="archive-panel patient-history-panel"><summary>Historial de Citas <span>${past.length}</span></summary><div class="list archive-list">${past.length?past.map(a=>`<div class="item compact-item"><div><strong>${esc(a.type)}</strong><span class="muted">${fmtDate(a.starts_at)} · ${esc(a.cancellation_reason?`Cancelación: ${a.cancellation_reason}`:(a.reason||'Sin Motivo Registrado'))}</span></div><span class="pill ${a.status==='confirmed'||a.status==='completed'?'green':(a.status==='rejected'||a.status==='cancelled')?'red':'amber'}">${esc(statusLabel(a.status))}</span></div>`).join(''):'<div class="empty">Todavía No Tiene Historial.</div>'}</div></details>`;
  const normalizeAppointmentType=value=>String(value||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').trim().toLowerCase();
  const resolveStoredAppointmentType=async(date,requestedType)=>{
    const dayOfWeek=new Date(`${date}T12:00:00-06:00`).getDay();
    const {data:rules,error}=await sb.from('availability_rules').select('appointment_type').eq('day_of_week',dayOfWeek).eq('active',true);
    if(error||!rules?.length)return requestedType;
    const wanted=normalizeAppointmentType(requestedType);
    const aliases={
      'consulta presencial':['consulta presencial','presencial','consulta'],
      'cita virtual':['cita virtual','consulta virtual','virtual','videollamada'],
      'visita domiciliaria':['visita domiciliaria','consulta domiciliaria','domiciliaria','a domicilio','visita a domicilio']
    };
    const wantedGroup=Object.entries(aliases).find(([,values])=>values.includes(wanted))?.[1]||[wanted];
    const exact=rules.find(r=>normalizeAppointmentType(r.appointment_type)===wanted);
    if(exact)return exact.appointment_type;
    const equivalent=rules.find(r=>wantedGroup.includes(normalizeAppointmentType(r.appointment_type)));
    return equivalent?.appointment_type||requestedType;
  };
  const loadSlots=async()=>{
    const date=$('#aDay').value,requestedType=$('#aType').value,grid=$('#slotGrid');
    $('#aSelectedSlot').value='';
    grid.innerHTML='<div class="empty">Consultando Horarios Disponibles…</div>';
    const storedType=await resolveStoredAppointmentType(date,requestedType);
    let {data,error}=await sb.rpc('get_available_slots',{p_date:date,p_type:storedType});
    if(!error&&!data?.length&&storedType!==requestedType){
      ({data,error}=await sb.rpc('get_available_slots',{p_date:date,p_type:requestedType}));
    }
    if(error){grid.innerHTML=`<div class="alert-banner">No Se Pudieron Consultar los Horarios: ${esc(error.message)}</div>`;return}
    if(!data?.length){grid.innerHTML='<div class="empty"><strong>No Hay Horarios Disponibles</strong><span>Pruebe otra fecha o tipo de atención.</span></div>';return}
    grid.innerHTML=data.map(x=>`<button type="button" class="slot-btn" data-start="${esc(x.starts_at)}" data-duration="${Number(x.duration_minutes||30)}"><strong>${formatSlotTime(x.starts_at)}</strong><span>${Number(x.duration_minutes||30)} Minutos</span></button>`).join('');
    grid.querySelectorAll('.slot-btn').forEach(btn=>btn.onclick=()=>{grid.querySelectorAll('.slot-btn').forEach(b=>b.classList.remove('selected'));btn.classList.add('selected');$('#aSelectedSlot').value=btn.dataset.start;$('#aSelectedDuration').value=btn.dataset.duration});
  };
  document.querySelectorAll('.appointment-type-option').forEach(btn=>btn.onclick=async()=>{document.querySelectorAll('.appointment-type-option').forEach(x=>x.classList.remove('selected'));btn.classList.add('selected');$('#aType').value=btn.dataset.type;await loadSlots();});
  $('#aDay').onchange=loadSlots;await loadSlots();
  $('#requestAppointmentBtn').onclick=async()=>{const startsAt=$('#aSelectedSlot').value,reason=$('#aReason').value.trim();if(!startsAt)return toast('Seleccione un Horario Disponible.');if(!reason)return toast('Escriba el Motivo de la Consulta.');const {error}=await sb.from('appointments').insert({patient_id:session.user.id,family_member_id:activeFamilyMemberId,type:$('#aType').value,starts_at:startsAt,duration_minutes:Number($('#aSelectedDuration').value||30),reason,status:'requested'});if(error){const msg=error.message?.includes('horario ya no está disponible')?'Ese Horario Acaba de Ser Ocupado. Seleccione Otro.':'No Se Pudo Enviar la Solicitud. Intente Nuevamente.';return toast(msg)}toast('Solicitud Enviada Correctamente.');render();};
}
window.cancelPatientAppointment=async id=>{
  const reason=$(`#cancelReason-${id}`)?.value||'';
  const note=$(`#cancelNote-${id}`)?.value.trim()||'';
  if(!reason)return toast('Seleccione el Motivo de Cancelación.');
  if(!confirm('¿Confirma que desea cancelar esta cita?'))return;
  const cancellation_reason=note?`${reason}: ${note}`:reason;
  const button=document.querySelector(`[data-action="cancel-patient-appointment"][data-id="${id}"]`);
  if(button){button.disabled=true;button.textContent='Cancelando…';}
  try{
    const {data,error}=await sb.rpc('cancel_own_appointment',{p_appointment_id:id,p_reason:cancellation_reason});
    if(error)throw error;
    if(data===false)throw new Error('La cita no pudo cancelarse. Puede que ya haya sido modificada.');
    toast('Cita Cancelada. Su médico recibió una alerta interna.');
    await render();
  }catch(error){
    if(button){button.disabled=false;button.textContent='Confirmar Cancelación';}
    toast(error.message||'No se pudo cancelar la cita.');
  }
};

async function patientDocuments(){
  const {data}=await familyFilter(sb.from('documents').select('*').eq('patient_id',session.user.id)).order('created_at',{ascending:false});
  const allDocs=data||[];
  const sent=allDocs.filter(d=>d.uploaded_by===session.user.id);
  const received=allDocs.filter(d=>d.uploaded_by!==session.user.id);
  const categoryOrder=['payment_receipt','prescription','consultation_summary','lab_order','image_order','instructions','results_report','lab_result','study','other'];
  const receivedGroups=categoryOrder.map(type=>({type,items:received.filter(d=>d.document_type===type)})).filter(g=>g.items.length);
  const receivedMarkup=receivedGroups.length?receivedGroups.map(g=>`<details class="document-category" ${receivedGroups.length===1?'open':''}><summary><span>${esc(documentTypeLabel(g.type))}</span><b>${g.items.length}</b></summary><div class="list document-category-list">${g.items.map(d=>`<div class="item"><div class="item-main"><strong>${esc(d.title)}</strong><span class="muted">Recibido de su médico · ${fmtDate(d.created_at)}</span></div><div class="item-actions document-share-actions"><button class="btn secondary" data-action="open-doc" data-path="${esc(d.storage_path)}">Abrir</button><button class="btn secondary" data-action="download-doc" data-path="${esc(d.storage_path)}" data-title="${esc(d.title)}">Descargar</button><button class="btn primary" data-action="share-doc" data-path="${esc(d.storage_path)}" data-title="${esc(d.title)}">Compartir</button></div></div>`).join('')}</div></details>`).join(''):'<div class="empty polished">Todavía no ha recibido documentos de su médico.</div>';
  main.innerHTML=`
    <div class="section-title major-heading"><h2>Mis Documentos</h2><span class="section-note">Expediente Digital</span></div>
    <div class="document-intro"><span>${ICONS.documents}</span><div><strong>Todo organizado en un solo lugar</strong><p>Consulte por separado lo que recibió del médico y lo que usted envió.</p></div></div>
    <details class="document-direction-panel received-panel" open>
      <summary><span class="direction-panel-icon">${ICONS.inbox}</span><span><strong>Documentos Recibidos</strong><small>Recetas, indicaciones, órdenes y reportes</small></span><b>${received.length}</b></summary>
      <div class="document-direction-body"><div class="document-groups">${receivedMarkup}</div></div>
    </details>
    <details class="document-direction-panel sent-panel">
      <summary><span class="direction-panel-icon">${ICONS.documents}</span><span><strong>Enviados por Usted</strong><small>Exámenes, fotografías y resultados compartidos</small></span><b>${sent.length}</b></summary>
      <div class="document-direction-body"><div class="list">${sent.length?sent.map(d=>`<div class="item"><div class="item-main"><strong>${esc(d.title)}</strong><span class="muted">${esc(documentTypeLabel(d.document_type))} · ${fmtDate(d.created_at)}</span></div><div class="item-actions document-share-actions"><button class="btn secondary" data-action="open-doc" data-path="${esc(d.storage_path)}">Abrir</button><button class="btn secondary" data-action="download-doc" data-path="${esc(d.storage_path)}" data-title="${esc(d.title)}">Descargar</button><button class="btn primary" data-action="share-doc" data-path="${esc(d.storage_path)}" data-title="${esc(d.title)}">Compartir</button></div></div>`).join(''):'<div class="empty">Todavía no ha enviado documentos.</div>'}</div></div>
    </details>
    <details class="upload-panel upload-panel-celeste">
      <summary><span class="upload-summary-icon">${ICONS.documents}</span><span><strong>ENVIAR EXÁMENES</strong><small>PDF, Fotografías o Resultados</small></span><b>⌄</b></summary>
      <div class="card upload-card upload-card-inside">
        <form id="docForm">
          <div class="form-grid">
            <div class="field"><label>Tipo de Documento</label><select id="dType"><option value="lab_result">Resultado de Laboratorio</option><option value="study">Estudio o Imagen</option><option value="other">Otro Documento</option></select></div>
            <div class="field"><label>Descripción</label><input id="dTitle" placeholder="Ejemplo: Hemograma de junio" required></div>
          </div>
          <div class="upload-actions">
            <button class="upload-option" id="chooseFileBtn" type="button"><span class="upload-option-icon">${ICONS.documents}</span><span><strong>Elegir PDF o Imagen</strong><small>PDF, JPG, PNG, HEIC o HEIF · máximo 15 MB</small></span></button>
            <button class="upload-option camera-option" id="takePhotoBtn" type="button"><span class="upload-option-icon">${ICONS.controls}</span><span><strong>Tomar una Foto</strong><small>Use la cámara del teléfono</small></span></button>
            <input id="dFile" class="file-picker-accessible" type="file" accept="application/pdf,image/jpeg,image/png,image/heic,image/heif,image/webp,image/*">
            <input id="dCamera" class="file-picker-accessible" type="file" accept="image/*" capture="environment">
          </div>
          <div id="selectedFileBox" class="selected-file hidden"></div>
          <div id="uploadStatus" class="muted upload-status">Seleccione un Archivo o Tome una Fotografía.</div>
          <button id="uploadPatientDocBtn" class="btn primary full upload-exams-celeste" type="submit" disabled>Enviar Exámenes</button>
        </form>
      </div>
    </details>`;

  const fileInput=$('#dFile'),cameraInput=$('#dCamera'),button=$('#uploadPatientDocBtn');
  let selectedPatientFile=null;
  const applySelectedFile=(input,source='archivo')=>{const f=input?.files?.[0]||null;if(!f)return false;selectedPatientFile=f;if(input===fileInput)cameraInput.value='';else fileInput.value='';const box=$('#selectedFileBox');box.classList.remove('hidden');box.innerHTML=`<strong>${esc(f.name||'Fotografía tomada')}</strong><span>${formatFileSize(f.size)} · ${esc(f.type||'Tipo detectado por el dispositivo')}</span>`;button.disabled=false;$('#uploadStatus').textContent=`${source==='camera'?'Fotografía':'Archivo'} listo para enviar.`;return true;};
  const recoverSelectedFile=()=>{if(selectedPatientFile)return true;return applySelectedFile(fileInput,'archivo')||applySelectedFile(cameraInput,'camera');};
  const openPicker=(input,label)=>{$('#uploadStatus').textContent=label;try{if(typeof input.showPicker==='function')input.showPicker();else input.click();}catch(_){input.click();}};
  $('#chooseFileBtn').addEventListener('click',()=>openPicker(fileInput,'Abriendo los archivos del dispositivo…'));
  $('#takePhotoBtn').addEventListener('click',()=>openPicker(cameraInput,'Abriendo la cámara…'));
  ['change','input'].forEach(evt=>{fileInput.addEventListener(evt,()=>applySelectedFile(fileInput,'archivo'));cameraInput.addEventListener(evt,()=>applySelectedFile(cameraInput,'camera'));});
  const resumeCheck=()=>{setTimeout(recoverSelectedFile,150);setTimeout(recoverSelectedFile,600);setTimeout(recoverSelectedFile,1200)};
  window.addEventListener('focus',resumeCheck,{once:true});
  document.addEventListener('visibilitychange',()=>{if(document.visibilityState==='visible')resumeCheck()},{once:true});
  window.addEventListener('pageshow',resumeCheck,{once:true});
  $('#docForm').addEventListener('submit',async e=>{e.preventDefault();recoverSelectedFile();await uploadPatientDoc(e,selectedPatientFile);});
}
function formatFileSize(bytes){if(!Number.isFinite(bytes))return '';if(bytes<1024)return `${bytes} B`;if(bytes<1024*1024)return `${(bytes/1024).toFixed(1)} KB`;return `${(bytes/1024/1024).toFixed(1)} MB`}
function translateUploadError(message=''){
  const m=String(message).toLowerCase();
  if(m.includes('row-level security')||m.includes('violates'))return 'No tiene permiso para subir este archivo. Ejecute la migración de almacenamiento V4.2 en Supabase.';
  if(m.includes('bucket')&&m.includes('not found'))return 'No se encontró el almacenamiento de documentos. Ejecute la migración V4.2.';
  if(m.includes('maximum')||m.includes('too large')||m.includes('payload'))return 'El archivo es demasiado grande. El límite es 15 MB.';
  if(m.includes('network')||m.includes('fetch'))return 'No se pudo conectar. Revise su conexión e intente nuevamente.';
  return message||'No se pudo subir el archivo.';
}
async function uploadPatientDoc(e,selectedFile=null){
  e.preventDefault();
  const fileInput=$('#dFile'),cameraInput=$('#dCamera');
  const f=selectedFile||fileInput?.files?.[0]||cameraInput?.files?.[0];
  const title=$('#dTitle').value.trim();
  const status=$('#uploadStatus'),btn=$('#uploadPatientDocBtn');
  if(!f)return toast('Seleccione un PDF, una imagen o tome una foto.');
  if(!title)return toast('Escriba una descripción del documento.');
  if(f.size>15*1024*1024)return toast('El archivo supera el límite de 15 MB.');
  const allowed=['application/pdf','image/jpeg','image/png','image/heic','image/heif'];
  if(f.type&&!allowed.includes(f.type)&&!f.type.startsWith('image/'))return toast('Formato no admitido. Use PDF o una imagen.');
  btn.disabled=true;btn.textContent='Enviando…';status.textContent='Cargando archivo de forma segura…';
  const safeName=(f.name||`foto-${Date.now()}.jpg`).replace(/[^a-zA-Z0-9._-]/g,'_');
  const path=`${session.user.id}/${crypto.randomUUID()}-${safeName}`;
  let {error}=await sb.storage.from('medical-documents').upload(path,f,{contentType:f.type||'application/octet-stream',cacheControl:'3600',upsert:false});
  if(error){btn.disabled=false;btn.textContent='Enviar Exámenes';status.textContent='';return toast(translateUploadError(error.message));}
  ({error}=await sb.from('documents').insert({patient_id:session.user.id,family_member_id:activeFamilyMemberId,uploaded_by:session.user.id,title,document_type:$('#dType').value,storage_path:path,reviewed:false}));
  if(error){await sb.storage.from('medical-documents').remove([path]);btn.disabled=false;btn.textContent='Enviar Exámenes';status.textContent='';return toast(translateUploadError(error.message));}
  status.textContent='Documento enviado correctamente.';toast('Documento enviado correctamente.');await render();
}
function documentFileName(title='',path=''){
  const rawName=String(path||'').split('/').pop()||'';
  const ext=(rawName.match(/\.[a-zA-Z0-9]{2,5}$/)||['.pdf'])[0].toLowerCase();
  const clean=String(title||'Documento_BernardHealth').normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-zA-Z0-9_-]+/g,'_').replace(/^_+|_+$/g,'').slice(0,70)||'Documento_BernardHealth';
  return clean.endsWith(ext)?clean:`${clean}${ext}`;
}
function normalizeMedicalDocumentPath(path=''){
  let value=String(path||'').trim();
  if(!value)return '';
  try{value=decodeURIComponent(value)}catch(_){ }
  value=value.replace(/^https?:\/\/[^/]+\/storage\/v1\/object\/(?:sign|public|authenticated)\/medical-documents\//i,'');
  value=value.replace(/^https?:\/\/[^/]+\/storage\/v1\/object\/medical-documents\//i,'');
  value=value.replace(/^\/?medical-documents\//i,'').replace(/^\/+/, '');
  return value;
}
async function createMedicalDocumentSignedUrl(path,expires=600){
  const cleanPath=normalizeMedicalDocumentPath(path);
  if(!cleanPath)throw new Error('Este documento no tiene una ruta válida.');
  const {data,error}=await sb.storage.from('medical-documents').createSignedUrl(cleanPath,expires);
  if(error)throw new Error(error.message==='Object not found'?'No se encontró el archivo. Ejecute el SQL de acceso a documentos incluido en esta versión.':error.message);
  if(!data?.signedUrl)throw new Error('No se pudo generar el enlace seguro del documento.');
  return {signedUrl:data.signedUrl,path:cleanPath};
}
async function getDocumentShareData(path,title='Documento médico'){
  const signed=await createMedicalDocumentSignedUrl(path,600);
  const response=await fetch(signed.signedUrl,{cache:'no-store'});if(!response.ok)throw new Error('No se pudo descargar el archivo desde el almacenamiento.');
  const blob=await response.blob();const name=documentFileName(title,signed.path);const type=blob.type||((name.toLowerCase().endsWith('.pdf'))?'application/pdf':'application/octet-stream');
  return {url:signed.signedUrl,blob,file:new File([blob],name,{type}),name};
}
window.openDoc=async path=>{
  const preview=window.open('about:blank','_blank');
  try{saveAdminContext();const {signedUrl}=await createMedicalDocumentSignedUrl(path,300);if(preview){preview.opener=null;preview.location.replace(signedUrl)}else window.location.href=signedUrl;}
  catch(err){if(preview)preview.close();console.error(err);toast(err.message||'No se pudo abrir el documento.');}
}
window.downloadDoc=async(path,title)=>{try{const {blob,name}=await getDocumentShareData(path,title);const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),30000);toast('Documento descargado.');}catch(err){console.error(err);toast(err.message||'No se pudo descargar el documento.');}}
window.shareDoc=async(path,title)=>{try{const data=await getDocumentShareData(path,title);if(navigator.share&&navigator.canShare?.({files:[data.file]})){await navigator.share({title:title||'Documento médico',text:'Documento compartido desde BernardHealth.',files:[data.file]});return;}if(navigator.share){await navigator.share({title:title||'Documento médico',text:'Documento compartido desde BernardHealth.',url:data.url});return;}const url=URL.createObjectURL(data.blob);const a=document.createElement('a');a.href=url;a.download=data.name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),30000);toast('El documento fue descargado porque este dispositivo no permite compartirlo directamente.');}catch(err){if(err?.name==='AbortError')return;console.error(err);toast(err.message||'No se pudo compartir el documento.');}}
async function patientBenefits(){
  const now=new Date().toISOString();
  const [{data:c},{data:camp},{data:ads},{data:benefits},{data:contacts},{data:redemptions}]=await Promise.all([
    sb.from('coupons').select('*').eq('active',true).gte('expires_at',now).order('expires_at'),
    sb.from('campaigns').select('*').eq('active',true).gte('ends_at',now),
    sb.from('advertisements').select('*').eq('active',true).gte('ends_at',now).order('created_at',{ascending:false}),
    sb.from('membership_benefits').select('*').eq('active',true).order('sort_order',{ascending:true}),
    familyFilter(sb.from('virtual_contacts').select('*').eq('patient_id',session.user.id)).order('created_at',{ascending:false}).limit(20),
    sb.from('coupon_redemptions').select('*,coupons(title,code,expires_at)').eq('patient_id',session.user.id).order('requested_at',{ascending:false})
  ]);
  const redemptionByCoupon=preferredRedemptionMap(redemptions||[]);
  main.innerHTML=`
    <section class="hero"><h2>Servicios y Beneficios</h2><p>Citas, campañas, cupones, promociones y herramientas para facilitar su atención.</p></section>
    <div class="grid cards">
      <div class="card"><h3>Beneficios Disponibles</h3><div class="metric">${(benefits?.length||0)+(c?.length||0)}</div><div class="muted">Servicios y cupones activos</div></div>
      <div class="card"><h3>Campañas Activas</h3><div class="metric">${camp?.length||0}</div><div class="muted">Oportunidades de atención y prevención</div></div>
    </div>
    <div id="virtualContactSection" class="section-title major-heading"><h2>Solicitar Cita Virtual</h2></div>
    <div class="card">
      <div class="form-grid">
        <div class="field"><label>Motivo</label><select id="vcType"><option value="treatment_question">Duda sobre tratamiento</option><option value="follow_up">Seguimiento de evolución</option><option value="results_question">Consulta sobre resultados</option><option value="control_review">Revisión de presión, glucosa o peso</option><option value="other">Otro motivo breve</option></select></div>
        <div class="field"><label>Horario preferido</label><input id="vcPreferred" type="datetime-local"></div>
      </div>
      <div class="field"><label>Explique brevemente su consulta</label><textarea id="vcMessage" maxlength="700" placeholder="Describa la duda en forma breve y clara."></textarea></div>
      <button class="btn primary" data-action="request-virtual-contact">Solicitar Cita Virtual</button>
      <p class="muted">Su médico revisará la solicitud y le dará seguimiento.</p>
    </div>
    <div class="section-title"><h2>Mis Solicitudes Virtuales</h2></div>
    <div class="list">${contacts?.length?contacts.map(x=>`<div class="item"><div><strong>${esc(virtualContactTypeLabel(x.request_type))}</strong><span class="muted">${esc(x.message||'Sin detalle')} · ${fmtDate(x.created_at)}</span></div><span class="pill">${esc(virtualContactStatusLabel(x.status))}</span></div>`).join(''):'<div class="empty">Aún no tiene solicitudes virtuales.</div>'}</div>
    <div class="section-title"><h2>Beneficios Disponibles</h2></div>
    <div class="benefit-grid interactive-benefits">${benefits?.length?benefits.map(x=>{const txt=`${x.title||''} ${x.description||''}`.toLowerCase();const action=txt.includes('virtual')?'<button class="btn primary" data-scroll-target="virtualContactSection">Solicitar Cita Virtual</button>':txt.includes('cupón')||txt.includes('cupon')?'<button class="btn primary" data-scroll-target="couponSection">Ver cupones</button>':txt.includes('cita')||txt.includes('consulta')?'<button class="btn primary" data-go="appointments">Agendar cita</button>':txt.includes('document')||txt.includes('examen')||txt.includes('resultado')?'<button class="btn primary" data-go="documents">Ir a documentos</button>':txt.includes('control')?'<button class="btn primary" data-go="controls">Registrar control</button>':'';return `<details class="benefit-card benefit-detail"><summary><span class="benefit-card-icon">${benefitIcon(x.icon_key)}</span><span class="benefit-card-content"><strong>${esc(neutralizeBrandText(x.title))}</strong><small>Ver beneficio</small></span><b>⌄</b></summary><div class="benefit-detail-body"><p>${esc(neutralizeBrandText(x.description||'Consulte las condiciones de este beneficio con su médico.'))}</p>${action}</div></details>`}).join(''):'<div class="empty">Los beneficios se publicarán próximamente.</div>'}</div>
    <div id="couponSection" class="section-title"><h2>Cupones</h2><p>Beneficios Disponibles para nuestros pacientes.</p></div><div class="promo-grid">${c?.length?c.map(x=>{const r=redemptionByCoupon.get(x.id);return `<article class="promo-card">${x.image_url?`<img class="promo-image" src="${esc(x.image_url)}" alt="${esc(x.title)}">`:''}<div class="promo-card-body"><span class="promo-label">Cupón</span><h3>${esc(x.title)}</h3><p>${esc(x.description||'')}</p><div class="coupon-code">${esc(x.code)}</div><small>Válido hasta ${fmtDate(x.expires_at)}</small>${r?`<span class="pill">${esc(couponStatusLabel(r.status))}</span>`:`<button class="btn primary full" data-action="request-coupon" data-id="${x.id}">Solicitar cupón</button>`}</div></article>`}).join(''):'<div class="empty">No hay cupones activos.</div>'}</div>
    <div class="section-title"><h2>Campañas y Promociones</h2><p>Información y oportunidades de salud disponibles.</p></div><div class="promo-grid">${camp?.length?camp.map(x=>`<article class="promo-card">${x.image_url?`<img class="promo-image" src="${esc(x.image_url)}" alt="${esc(x.title)}">`:''}<div class="promo-card-body"><span class="promo-label">Campaña</span><h3>${esc(neutralizeBrandText(x.title))}</h3><p>${esc(neutralizeBrandText(x.description||''))}</p><small>Disponible hasta ${fmtDate(x.ends_at)}</small><button class="btn primary full" data-action="campaign-cta" data-destination="${x.action_type==='external'?'external':x.action_type==='coupons'?'coupons':'appointments'}" data-url="${esc(x.action_url||'')}">${x.action_type==='external'?'Más información':x.action_type==='coupons'?'Ver cupones':'Solicitar cita'}</button></div></article>`).join(''):'<div class="empty">No hay campañas activas.</div>'}</div>
    <div class="section-title"><h2>Publicidad</h2><p>Empresas y servicios recomendados para nuestros pacientes.</p></div><div class="promo-grid">${ads?.length?ads.map(x=>`<article class="promo-card">${x.image_url?`<img class="promo-image" src="${esc(x.image_url)}" alt="${esc(x.title)}">`:''}<div class="promo-card-body"><span class="promo-label">Publicidad</span><h3>${esc(x.title)}</h3><p>${esc(x.description||'')}</p>${x.contact_text?`<strong>${esc(x.contact_text)}</strong>`:''}<small>Disponible hasta ${fmtDate(x.ends_at)}</small>${x.action_url?`<button class="btn primary full" data-action="open-ad-link" data-url="${esc(x.action_url)}">Más información</button>`:''}</div></article>`).join(''):'<div class="empty">No hay publicidad activa.</div>'}</div>`;
}
async function renderAdmin(){
  if(currentView==='patient-detail'&&activePatientId){await openPatient(activePatientId);return;}
  const routes={home:adminHome,patients:adminPatients,'inactive-patients':adminInactivePatients,'inactive-patient-detail':adminInactivePatientDetail,agenda:adminAgenda,inbox:adminInbox,billing:adminBilling,more:adminMore};
  await (routes[currentView]||adminHome)();
}
async function adminHome(){
  const nowIso=new Date().toISOString();
  const today=new Date();today.setHours(0,0,0,0);const tomorrow=new Date(today);tomorrow.setDate(tomorrow.getDate()+1);
  const [{count:p},{count:familyCount},{count:inactiveCount},{data:reqRows},{count:docs},{count:mem},{data:todayA}]=await Promise.all([
    sb.from('profiles').select('*',{count:'exact',head:true}).eq('role','patient'),
    sb.from('family_members').select('*',{count:'exact',head:true}).eq('active',true),
    sb.from('inactive_patients').select('*',{count:'exact',head:true}).neq('status','activated'),
    sb.from('appointments').select('id').eq('status','requested').gte('starts_at',nowIso),
    sb.from('documents').select('*',{count:'exact',head:true}).eq('reviewed',false),
    sb.from('memberships').select('*',{count:'exact',head:true}).eq('status','active'),
    sb.from('appointments').select('*,profiles!appointments_patient_id_fkey(full_name),family_members(full_name)').gte('starts_at',today.toISOString()).lt('starts_at',tomorrow.toISOString()).order('starts_at')
  ]);
  const nowTs=Date.now();
  const activeToday=(todayA||[]).filter(a=>['requested','confirmed'].includes(a.status)&&new Date(a.starts_at).getTime()>=nowTs);
  const elapsedToday=(todayA||[]).filter(a=>new Date(a.starts_at).getTime()<nowTs).sort((a,b)=>new Date(b.starts_at)-new Date(a.starts_at));
  main.innerHTML=`
    <section class="admin-welcome"><div><span class="eyebrow light">PANEL MÉDICO</span><h2>${greetingByHour()}</h2><p>Controle pacientes, agenda, documentos y beneficios desde su celular.</p></div><div class="admin-mark">${ICONS.controls}</div></section>
    <div class="admin-metrics">
      <button class="admin-stat" data-go="patients"><span class="stat-icon lilac">${ICONS.patients}</span><div><small>Pacientes</small><strong>${(p||0)+(familyCount||0)}</strong><p>Cuentas y familiares registrados</p></div><b>›</b></button>
      <button class="admin-stat inactive-stat" data-go="inactive-patients"><span class="stat-icon rose">${ICONS.patients}</span><div><small>Pacientes inactivos</small><strong>${inactiveCount||0}</strong><p>Perfiles pendientes de activar</p></div><b>›</b></button>
      <button class="admin-stat" data-go="agenda"><span class="stat-icon amber">${ICONS.agenda}</span><div><small>Citas pendientes</small><strong>${reqRows?.length||0}</strong><p>Solicitudes por revisar</p></div><b>›</b></button>
      <button class="admin-stat" data-go="inbox"><span class="stat-icon mint">${ICONS.documents}</span><div><small>Documentos nuevos</small><strong>${docs||0}</strong><p>Resultados pendientes</p></div><b>›</b></button>
      <button class="admin-stat" data-go="patients"><span class="stat-icon rose">${ICONS.benefits}</span><div><small>Pacientes verificados</small><strong>${mem||0}</strong><p>Accesos aprobados</p></div><b>›</b></button>
    </div>
    <div class="section-title"><h2>Accesos Rápidos</h2></div>
    <div class="admin-shortcuts">
      <button data-go="patients"><span>${ICONS.patients}</span><strong>Pacientes activos</strong><small>Abrir ficha clínica</small></button>
      <button class="inactive-shortcut" data-go="inactive-patients"><span>${ICONS.patients}</span><strong>Pacientes inactivos</strong><small>Crear perfil e invitar por WhatsApp</small></button>
      <button data-go="agenda"><span>${ICONS.agenda}</span><strong>Gestionar agenda</strong><small>Confirmar solicitudes</small></button>
      <button data-go="inbox"><span>${ICONS.inbox}</span><strong>Revisar alertas</strong><small>Resultados y controles</small></button>
      <button data-go="more"><span>${ICONS.benefits}</span><strong>Gestión y comunicación</strong><small>Alertas, beneficios, cupones y anuncios</small></button>
      <button data-go="billing"><span>${ICONS.billing}</span><strong>Facturación Interna</strong><small>Comprobantes, ingresos y reportes</small></button>
    </div>
    <div class="section-title today-heading counter-heading"><div><span class="today-kicker">PRIORIDAD DEL DÍA</span><h2>Próximas citas de hoy</h2></div><span class="counter-badge counter-badge-white"><strong>${activeToday.length}</strong><small>pendiente${activeToday.length===1?'':'s'}</small></span></div>
    <div class="list agenda-list">${activeToday.length?activeToday.map(a=>`<div class="item"><span class="time-badge">${new Intl.DateTimeFormat('es-CR',{hour:'numeric',minute:'2-digit'}).format(new Date(a.starts_at))}</span><div class="item-main"><strong>${esc(personName(a.profiles?.full_name,a.family_members))}</strong><span class="muted">${esc(a.type)}</span></div><span class="pill ${a.status==='confirmed'?'green':'amber'}">${statusLabel(a.status)}</span></div>`).join(''):'<div class="empty polished">No quedan citas pendientes para hoy.</div>'}</div>
    ${elapsedToday.length?`<details class="archive-panel"><summary>Citas de hoy ya transcurridas <span>${elapsedToday.length}</span></summary><div class="list archive-list">${elapsedToday.map(a=>`<div class="item compact-item"><span class="time-badge">${new Intl.DateTimeFormat('es-CR',{hour:'numeric',minute:'2-digit'}).format(new Date(a.starts_at))}</span><div class="item-main"><strong>${esc(personName(a.profiles?.full_name,a.family_members))}</strong><span class="muted">${esc(a.type)}</span></div><span class="pill ${a.status==='confirmed'?'green':'amber'}">${statusLabel(a.status)}</span></div>`).join('')}</div></details>`:''}`;
}
async function adminPatients(){
  const [{data,error},{data:family,error:fErr}]=await Promise.all([sb.from('profiles').select('*,memberships(*)').eq('role','patient').order('full_name'),sb.from('family_members').select('*').eq('active',true).order('full_name')]);
  if(error||fErr) throw (error||fErr);
  const familyByOwner=new Map();(family||[]).forEach(m=>{const a=familyByOwner.get(m.owner_user_id)||[];a.push(m);familyByOwner.set(m.owner_user_id,a)});
  main.innerHTML=`<div class="section-title"><h2>Pacientes</h2></div><div class="field"><input id="patientSearch" placeholder="Buscar por nombre, familiar, correo o teléfono"></div><div id="patientList" class="list" style="margin-top:12px"></div>`;
  const patients=data||[];
  const draw=q=>{const list=$('#patientList');list.innerHTML=patients.filter(x=>{const fam=familyByOwner.get(x.id)||[];return !q||`${x.full_name||''} ${x.email||''} ${x.whatsapp_phone||''} ${fam.map(m=>m.full_name).join(' ')}`.toLowerCase().includes(q.toLowerCase())}).map(x=>{const fam=familyByOwner.get(x.id)||[];return `<div class="item patient-list-item"><div class="patient-list-copy"><strong>${esc(x.full_name||'Sin nombre')}</strong><span class="muted">${esc(x.email||'Sin correo')} · ${esc(displayWhatsappPhone(x.whatsapp_phone))} · ${esc(membershipLabel(x.memberships?.[0]?.status))}</span>${fam.length?`<small class="family-count">Mi Familia: ${fam.length} perfil${fam.length===1?'':'es'} · ${esc(fam.map(m=>m.full_name).join(', '))}</small>`:''}</div><div class="item-actions patient-list-actions"><button type="button" class="btn primary js-open-patient" data-patient-id="${esc(x.id)}">Abrir</button><button type="button" class="btn danger" data-action="delete-patient" data-id="${esc(x.id)}" data-name="${esc(x.full_name||x.email||'este paciente')}">Eliminar</button></div></div>`}).join('')||'<div class="empty">No hay pacientes.</div>'};
  draw('');$('#patientSearch').oninput=e=>draw(e.target.value);$('#patientList').addEventListener('click',e=>{const btn=e.target.closest('.js-open-patient');if(!btn)return;e.preventDefault();e.stopPropagation();activeAdminFamilyMemberId=null;openPatient(btn.dataset.patientId,btn)});
}
function withTimeout(promise,label,ms=10000){
  return Promise.race([
    promise,
    new Promise((_,reject)=>setTimeout(()=>reject(new Error(`${label}: tiempo de espera agotado`)),ms))
  ]);
}

window.openPatient=async(id,sourceButton=null)=>{
  if(!id){toast('No se pudo identificar al paciente.');return;}
  pushNavigationState();
  if(sourceButton){sourceButton.disabled=true;sourceButton.textContent='Abriendo…';}
  currentView='patient-detail';
  activePatientId=id;
  saveAdminContext();
  main.innerHTML=`<button type="button" class="btn secondary" id="backPatientsBtn">← Volver</button><div class="section-title"><h2>Ficha del Paciente</h2></div><div class="card"><div class="loading-row"><span class="spinner" aria-hidden="true"></span><div><strong>Abriendo ficha…</strong><span class="muted">Cargando perfil y registros.</span></div></div></div>`;
  renderNav();
  const back=()=>navigateBack();
  $('#backPatientsBtn').onclick=back;
  try{
    const patientQ=await withTimeout(sb.from('profiles').select('*').eq('id',id).single(),'Perfil');
    if(patientQ.error)throw new Error(`Perfil: ${patientQ.error.message}`);
    const p=patientQ.data;
    main.innerHTML=`<button type="button" class="btn secondary" id="backPatientsBtn">← Volver</button><div class="section-title"><h2>${esc(p.full_name||'Paciente')}</h2></div><div class="patient-summary card"><div><strong>${esc(p.email||'Sin correo')}</strong><span class="muted">${p.identification?`Cédula: ${esc(p.identification)}`:'Cédula no registrada'}</span></div><div class="patient-summary-actions"><span class="pill">Cargando datos…</span><button type="button" class="btn secondary" id="editPatientIdentificationBtn">${p.identification?'Editar cédula':'Agregar cédula'}</button></div></div><div class="admin-identification-editor hidden" id="patientIdentificationEditor"><div class="field"><label>Identificación del paciente</label><input id="patientIdentificationInput" inputmode="numeric" value="${esc(p.identification||'')}" placeholder="Número de cédula o identificación"></div><button type="button" class="btn primary" id="savePatientIdentificationBtn">Guardar identificación</button></div><section class="admin-whatsapp-card"><div class="admin-whatsapp-copy"><span class="admin-whatsapp-icon">${ICONS.whatsapp||ICONS.inbox}</span><div><small>CONTACTO DIRECTO</small><strong>${esc(displayWhatsappPhone(p.whatsapp_phone))}</strong><span>Abra WhatsApp con un mensaje preparado para este paciente.</span></div></div><div class="admin-whatsapp-actions"><button type="button" class="btn whatsapp-admin-btn" id="contactPatientWhatsappBtn" ${p.whatsapp_phone?'':'disabled'}>Contactar por WhatsApp</button><button type="button" class="btn secondary" id="editPatientWhatsappBtn">${p.whatsapp_phone?'Editar número':'Agregar número'}</button></div><div class="admin-whatsapp-editor hidden" id="patientWhatsappEditor"><div class="field"><label>Número con código de país</label><input id="patientWhatsappInput" type="tel" inputmode="tel" value="${esc(p.whatsapp_phone||'')}" placeholder="Ejemplo: +506 8888 8888"></div><button type="button" class="btn primary" id="savePatientWhatsappBtn">Guardar número</button></div></section><div id="patientDetailBody"><div class="card"><div class="loading-row"><span class="spinner" aria-hidden="true"></span><div><strong>Cargando información clínica…</strong><span class="muted">Esto puede tardar unos segundos.</span></div></div></div></div>`;
    $('#backPatientsBtn').onclick=back;
    const editIdentificationBtn=$('#editPatientIdentificationBtn');
    if(editIdentificationBtn)editIdentificationBtn.onclick=()=>$('#patientIdentificationEditor').classList.toggle('hidden');
    const saveIdentificationBtn=$('#savePatientIdentificationBtn');
    if(saveIdentificationBtn)saveIdentificationBtn.onclick=async()=>{
      const identification=($('#patientIdentificationInput').value||'').trim();
      if(!identification)return toast('Escriba la identificación del paciente.');
      saveIdentificationBtn.disabled=true;saveIdentificationBtn.textContent='Guardando…';
      const {error}=await sb.from('profiles').update({identification}).eq('id',id);
      if(error){saveIdentificationBtn.disabled=false;saveIdentificationBtn.textContent='Guardar identificación';return toast(error.message);}
      toast('Identificación actualizada.');openPatient(id);
    };
    const contactBtn=$('#contactPatientWhatsappBtn');
    if(contactBtn)contactBtn.onclick=()=>{const url=whatsappUrl(p.whatsapp_phone,p.full_name);if(!url)return toast('Este paciente no tiene un número de WhatsApp registrado.');window.open(url,'_blank','noopener,noreferrer');};
    const editWhatsappBtn=$('#editPatientWhatsappBtn');
    if(editWhatsappBtn)editWhatsappBtn.onclick=()=>$('#patientWhatsappEditor').classList.toggle('hidden');
    const saveWhatsappBtn=$('#savePatientWhatsappBtn');
    if(saveWhatsappBtn)saveWhatsappBtn.onclick=async()=>{
      const phone=normalizeWhatsappPhone($('#patientWhatsappInput').value);
      if(phone.length<10)return toast('Escriba un número válido con código de país.');
      saveWhatsappBtn.disabled=true;saveWhatsappBtn.textContent='Guardando…';
      const {error}=await sb.from('profiles').update({whatsapp_phone:phone}).eq('id',id);
      if(error){saveWhatsappBtn.disabled=false;saveWhatsappBtn.textContent='Guardar número';return toast(error.message);}
      toast('Número de WhatsApp actualizado.');openPatient(id);
    };

    const familyQ=await sb.from('family_members').select('*').eq('owner_user_id',id).eq('active',true).order('full_name');
    const familyMembers=familyQ.data||[];
    const selectedMember=activeAdminFamilyMemberId?familyMembers.find(x=>x.id===activeAdminFamilyMemberId):null;
    const results=await Promise.allSettled([
      withTimeout(sb.from('memberships').select('*').eq('patient_id',id).maybeSingle(),'Acceso'),
      withTimeout(adminFamilyFilter(sb.from('documents').select('*').eq('patient_id',id)).order('created_at',{ascending:false}).limit(30),'Documentos'),
      withTimeout(adminFamilyFilter(sb.from('health_records').select('*').eq('patient_id',id).is('doctor_hidden_at',null)).order('recorded_at',{ascending:false}).limit(30),'Controles'),
      withTimeout(adminFamilyFilter(sb.from('appointments').select('*').eq('patient_id',id)).order('starts_at',{ascending:false}).limit(30),'Citas')
    ]);
    const unwrap=(res,label,fallback)=>{
      if(res.status==='rejected')return {data:fallback,error:`${label}: ${res.reason?.message||'Error desconocido'}`};
      if(res.value?.error)return {data:fallback,error:`${label}: ${res.value.error.message}`};
      return {data:res.value?.data??fallback,error:null};
    };
    const M=unwrap(results[0],'Acceso',null),D=unwrap(results[1],'Documentos',[]),R=unwrap(results[2],'Controles',[]),A=unwrap(results[3],'Citas',[]);
    const m=M.data,d=D.data||[],r=R.data||[],a=A.data||[];
    const patientSentDocs=d.filter(x=>x.uploaded_by===id);
    const doctorSentDocs=d.filter(x=>x.uploaded_by!==id);
    const doctorDocGroups=['payment_receipt','prescription','consultation_summary','lab_order','image_order','instructions','results_report','lab_result','study','other'].map(type=>({type,items:doctorSentDocs.filter(x=>x.document_type===type)})).filter(g=>g.items.length);
    const doctorDocsMarkup=doctorDocGroups.length?doctorDocGroups.map(g=>`<details class="document-category"><summary><span>${esc(documentTypeLabel(g.type))}</span><b>${g.items.length}</b></summary><div class="list document-category-list">${g.items.map(x=>`<div class="item admin-document-item"><div class="item-main"><strong>${esc(x.title)}</strong><span class="muted">Enviado al paciente · ${fmtDate(x.created_at)}</span></div><div class="item-actions admin-document-actions"><button type="button" class="btn secondary js-open-doc" data-path="${esc(x.storage_path)}">Abrir</button>${x.document_type==='payment_receipt'?`<small class="document-managed-note">Administrar desde Facturación</small>`:`<button type="button" class="btn ghost js-reassign-doc" data-id="${x.id}" data-title="${esc(x.title)}">Cambiar destinatario</button><button type="button" class="btn danger js-delete-doc" data-id="${x.id}" data-path="${esc(x.storage_path)}" data-title="${esc(x.title)}">Eliminar</button>`}</div></div>`).join('')}</div></details>`).join(''):'<div class="empty">No hay documentos enviados por el médico.</div>';
    const patientDocsMarkup=patientSentDocs.length?patientSentDocs.map(x=>`<div class="item"><div><strong>${esc(x.title)}</strong><span class="muted">${esc(documentTypeLabel(x.document_type))} · Enviado por el paciente · ${fmtDate(x.created_at)}</span></div><button type="button" class="btn secondary js-open-doc" data-path="${esc(x.storage_path)}">Abrir</button></div>`).join(''):'<div class="empty">El paciente todavía no ha enviado documentos.</div>';
    const errors=[M.error,D.error,R.error,A.error].filter(Boolean);
    const activePerson=selectedMember?{...p,full_name:selectedMember.full_name,identification:selectedMember.identification,sex:selectedMember.sex,family_member_id:selectedMember.id}:p;
    $('#patientDetailBody').innerHTML=`${familyMembers.length?`<section class="admin-family-selector"><div class="admin-family-copy"><span class="eyebrow">PERFIL DEL PACIENTE</span><h3>${esc(selectedMember?.full_name||p.full_name||'Paciente')}</h3><p>${selectedMember?esc(selectedMember.relationship||'Familiar'):'Titular de la Cuenta'}</p></div><div class="admin-family-picker"><label for="adminFamilySelect">Ver perfil de</label><select id="adminFamilySelect"><option value="self" ${!selectedMember?'selected':''}>${esc(p.full_name||'Titular')} · Titular</option>${familyMembers.map(m=>`<option value="${m.id}" ${selectedMember?.id===m.id?'selected':''}>${esc(m.full_name)} · ${esc(m.relationship||'Familiar')}</option>`).join('')}</select></div></section>`:''}${errors.length?`<div class="alert-banner"><strong>Algunos datos no pudieron cargarse.</strong><br><span class="muted">${errors.map(esc).join('<br>')}</span></div>`:''}${clinicalWorkspaceMarkup(activePerson)}${medicalDocumentBuilderMarkup(activePerson)}<div class="split"><div class="card"><h3>Acceso del paciente</h3><div class="field"><label>Estado</label><select id="mStatus"><option ${m?.status==='active'?'selected':''} value="active">Verificado</option><option ${m?.status==='inactive'?'selected':''} value="inactive">Pendiente</option><option ${m?.status==='suspended'?'selected':''} value="suspended">Suspendido</option></select></div><button type="button" class="btn primary" id="saveMembershipBtn">Guardar acceso</button><p class="muted" style="margin-top:10px">La verificación habilita el acceso del paciente sin cobros ni vencimiento.</p></div><div class="card"><h3>Enviar documento</h3><div class="field"><label>Título</label><input id="adTitle"></div><div class="field"><label>Tipo</label><select id="adType"><option value="prescription">Receta</option><option value="lab_order">Orden de laboratorio</option><option value="image_order">Orden de estudios de imagen</option><option value="instructions">Indicaciones</option><option value="consultation_summary">Resumen de consulta</option><option value="results_report">Reporte de resultados</option></select></div><div class="field"><label>PDF</label><input id="adFile" type="file" accept="application/pdf"></div><button type="button" class="btn primary" id="uploadDocBtn">Enviar al paciente</button></div></div><div class="section-title major-heading"><h2>Documentos del paciente</h2><span class="section-note">${d.length}</span></div><details class="document-direction-panel doctor-sent-panel" open><summary><span class="direction-panel-icon">${ICONS.documents}</span><span><strong>Enviados por el Médico</strong><small>Recetas, órdenes, indicaciones y reportes</small></span><b>${doctorSentDocs.length}</b></summary><div class="document-direction-body"><div class="document-groups">${doctorDocsMarkup}</div></div></details><details class="document-direction-panel patient-sent-panel"><summary><span class="direction-panel-icon">${ICONS.inbox}</span><span><strong>Enviados por el Paciente</strong><small>Exámenes, fotografías y resultados recibidos</small></span><b>${patientSentDocs.length}</b></summary><div class="document-direction-body"><div class="list">${patientDocsMarkup}</div></div></details><div class="section-title"><h2>Controles Recientes</h2></div><div class="list">${r.length?r.map(x=>`<div class="item"><div class="item-main"><strong>${esc(labelType(x.record_type))}: ${esc(x.value_text)}</strong><span class="muted">${fmtDate(x.recorded_at)} · ${esc(x.notes||'')}</span></div><button class="btn danger" data-action="hide-admin-control" data-id="${x.id}">Quitar de mi panel</button></div>`).join(''):'<div class="empty">Sin controles.</div>'}</div><div class="section-title"><h2>Citas</h2></div><div class="list">${a.length?a.map(x=>`<div class="item"><div><strong>${esc(x.type)}</strong><span class="muted">${fmtDate(x.starts_at)} · ${esc(x.reason||'')}</span></div><span class="pill">${esc(statusLabel(x.status))}</span></div>`).join(''):'<div class="empty">Sin citas.</div>'}</div>`;
    document.querySelector('.patient-summary .pill').textContent=membershipLabel(m?.status);
    document.querySelector('.patient-summary .pill').classList.toggle('green',m?.status==='active');
    $('#saveMembershipBtn').onclick=()=>saveMembership(id);
    $('#uploadDocBtn').onclick=()=>adminUploadDoc(id);
    initMedicalDocumentBuilder(activePerson);
    initClinicalWorkspace(activePerson);
    const familySelect=$('#adminFamilySelect');if(familySelect)familySelect.onchange=()=>{activeAdminFamilyMemberId=familySelect.value==='self'?null:familySelect.value;openPatient(id)};
    const openBillingBtn=$('#openBillingForPatientBtn');if(openBillingBtn)openBillingBtn.onclick=()=>{billingPrefillPatientId=id;billingPrefillFamilyMemberId=selectedMember?.id||null;currentView='billing';render();};
    document.querySelectorAll('.js-open-doc').forEach(b=>b.onclick=()=>{saveAdminContext();openDoc(b.dataset.path)});
    document.querySelectorAll('.js-reassign-doc').forEach(b=>b.onclick=()=>reassignAdminDocument(b.dataset.id,b.dataset.title,id));
    document.querySelectorAll('.js-delete-doc').forEach(b=>b.onclick=()=>deleteAdminDocument(b.dataset.id,b.dataset.path,b.dataset.title,id));
    if(restorePatientScrollY>0){const y=restorePatientScrollY;restorePatientScrollY=0;setTimeout(()=>window.scrollTo(0,y),80);}
  }catch(err){
    console.error('Error al abrir paciente:',err);
    main.innerHTML=`<button type="button" class="btn secondary" id="backPatientsBtn">← Volver</button><div class="alert-banner"><strong>No se pudo abrir la ficha del paciente.</strong><br><span class="muted">${esc(err.message)}</span></div>`;
    $('#backPatientsBtn').onclick=back;
    toast('No se pudo abrir la ficha.');
  }
}
window.deletePatient=async(id,name)=>{
  if(!confirm(`¿Eliminar definitivamente a ${name}? Esta acción borrará su acceso, citas, controles y documentos.`))return;
  const {data:docs}=await sb.from('documents').select('storage_path').eq('patient_id',id);
  const paths=(docs||[]).map(x=>x.storage_path).filter(Boolean);
  if(paths.length){const {error:storageError}=await sb.storage.from('medical-documents').remove(paths);if(storageError)console.warn('No se pudieron retirar todos los archivos:',storageError)}
  const {error}=await sb.rpc('admin_delete_patient',{p_patient_id:id});
  if(error)return toast(error.message);
  toast('Paciente eliminado correctamente.'); currentView='patients'; render();
};
window.saveMembership=async id=>{const payload={patient_id:id,status:$('#mStatus').value,starts_at:new Date().toISOString(),ends_at:'2099-12-31T23:59:59.000Z',monthly_price:null};const {error}=await sb.from('memberships').upsert(payload,{onConflict:'patient_id'});if(error)return toast(error.message);toast('Acceso del paciente actualizado.');openPatient(id)}

const MEDICAL_DOC_TYPES={
  consultation_summary:{title:'Resumen de Consulta',documentType:'consultation_summary',fields:[['Motivo de Consulta','textarea'],['Antecedentes Relevantes','textarea'],['Hallazgos Clínicos','textarea'],['Impresión Diagnóstica','textarea'],['Plan y Seguimiento','textarea']]},
  instructions:{title:'Indicaciones Médicas',documentType:'instructions',fields:[['Diagnóstico o Motivo','textarea'],['Indicaciones','textarea'],['Tratamiento','textarea'],['Cuidados y Recomendaciones','textarea'],['Signos de Alarma','textarea'],['Próximo Control','text']]},
  lab_order:{title:'Orden de Laboratorios',documentType:'lab_order',fields:[['Prioridad','select:Rutina|Preferente|Urgente'],['Preparación del Paciente','textarea'],['Observaciones Clínicas','textarea']]},
  image_order:{title:'Orden de Estudios de Imagen',documentType:'image_order',fields:[['Región Anatómica','text'],['Lado','select:No aplica|Derecho|Izquierdo|Bilateral'],['Con Contraste','select:No|Sí|Según criterio de Radiología'],['Prioridad','select:Rutina|Preferente|Urgente'],['Diagnóstico Presuntivo','textarea'],['Datos Clínicos Relevantes','textarea'],['Observaciones e Indicaciones','textarea']]},
  prescription:{title:'Receta Médica',documentType:'prescription',fields:[['Medicamentos e Indicaciones','textarea'],['Recomendaciones Adicionales','textarea'],['Duración del Tratamiento','text']]},
  attendance:{title:'Comprobante de Asistencia',documentType:'attendance_certificate',fields:[['Fecha y Hora de Atención','text'],['Motivo General de Atención','textarea'],['Observaciones','textarea']]},
  medical_report:{title:'Dictamen Médico',documentType:'medical_report',fields:[['Motivo del Dictamen','textarea'],['Antecedentes Relevantes','textarea'],['Hallazgos','textarea'],['Conclusión Médica','textarea'],['Recomendaciones','textarea']]},
  custom:{title:'Documento Personalizado',documentType:'other',fields:[['Título del Documento','text'],['Contenido','textarea'],['Observaciones','textarea']]}
};
const DOCTOR_PROFILE={name:'Dr. Abraham Benard Alvarado',title:'Médico y Cirujano',code:'10596',area:'Medicina General',phone:'6327-1260',email:'informacion@drbenard.com',web:'www.drbenard.com',address:'La Victoria, Horquetas de Sarapiquí, 200 metros este del INDER, Heredia, Costa Rica',business:'Doctor Benard Consulta Médica'};
const COMMON_LAB_GROUPS={
  'Hematología':['Hemograma Completo','Plaquetas','VSG','Reticulocitos','TP / INR','TPT'],
  'Glucosa y Diabetes':['Glicemia en Ayunas','Hemoglobina Glicosilada (HbA1c)','Insulina Basal'],
  'Función Renal y Electrolitos':['Creatinina','Nitrógeno Ureico (BUN)','Ácido Úrico','Sodio','Potasio','Cloro','Calcio','Magnesio'],
  'Perfil Lipídico':['Colesterol Total','HDL','LDL','Triglicéridos'],
  'Función Hepática':['TGO / AST','TGP / ALT','Bilirrubina Total y Directa','Fosfatasa Alcalina','GGT','Albúmina'],
  'Tiroides':['TSH','T4 Libre'],
  'Orina y Heces':['EGO','Urocultivo','Microalbuminuria','Examen General de Heces','Sangre Oculta en Heces'],
  'Inflamación e Infecciosos':['Proteína C Reactiva (PCR)','VIH','VDRL','HBsAg','Anti-HCV'],
  'Estudios de Imagen':['Ultrasonido','Radiografía','Tomografía Computarizada (TAC)','Resonancia Magnética (RMN)','Mamografía','Densitometría Ósea','Ecocardiograma','Doppler Vascular'],
  'Otros':['PSA','Ferritina','Vitamina B12','Vitamina D','Beta-hCG']
};

const COMMON_IMAGE_GROUPS={
  'Radiografías':['Tórax PA y lateral','Abdomen simple','Columna cervical','Columna dorsal','Columna lumbar','Pelvis','Hombro','Codo','Muñeca','Mano','Rodilla','Tobillo','Pie'],
  'Ultrasonidos':['Abdomen completo','Abdomen superior','Renal y vías urinarias','Pélvico','Transvaginal','Próstata y vejiga','Tiroides','Mama','Partes blandas','Testicular','Doppler venoso','Doppler arterial'],
  'Tomografía Computarizada':['Cráneo','Senos paranasales','Tórax','Abdomen','Abdomen y pelvis','Columna cervical','Columna lumbar','Angio-TAC'],
  'Resonancia Magnética':['Cerebro','Columna cervical','Columna dorsal','Columna lumbar','Hombro','Rodilla','Pelvis','Abdomen','Angio-RM'],
  'Estudios Especiales':['Mamografía','Densitometría ósea','Ecocardiograma','Doppler carotídeo','Doppler de miembros inferiores','Urografía','Serie gastroduodenal','Enema baritado']
};

function medicalDocumentBuilderMarkup(p){
  return `<details class="medical-doc-builder" id="medicalDocBuilder"><summary><span class="medical-doc-builder-icon">${ICONS.documents}</span><span><strong>Crear Documento Médico</strong><small>Genere un PDF membretado, firmado y listo para el paciente.</small></span><b>⌄</b></summary><div class="medical-doc-builder-body"><div class="medical-doc-grid"><div class="field"><label>Tipo de Documento</label><select id="medicalDocType">${Object.entries(MEDICAL_DOC_TYPES).map(([k,v])=>`<option value="${k}">${v.title}</option>`).join('')}</select></div><div class="field"><label>Identificación del Paciente</label><input id="medicalPatientId" placeholder="Opcional"></div><div class="field"><label>Sexo</label><select id="medicalPatientSex"><option value="">No indicado</option><option>Femenino</option><option>Masculino</option><option>Otro</option></select></div></div><div id="medicalPrescriptionMeta"></div><div id="medicalDocFields"></div><div class="medical-doc-actions"><button type="button" class="btn secondary" id="previewMedicalDocBtn">Previsualizar</button><button type="button" class="btn primary" id="generateMedicalDocBtn">Generar PDF y Enviar</button></div><p class="muted">El documento se guardará en el expediente y aparecerá en Documentos Recibidos del paciente.</p></div></details>`;
}
function medicalDocumentFieldsMarkup(type){
  const cfg=MEDICAL_DOC_TYPES[type]||MEDICAL_DOC_TYPES.consultation_summary;
  const standardFields=cfg.fields.map(([label,kind],i)=>{const id=`medicalField${i}`;if(kind==='textarea')return `<div class="field"><label>${label}</label><textarea id="${id}" rows="4"></textarea></div>`;if(kind.startsWith('select:'))return `<div class="field"><label>${label}</label><select id="${id}">${kind.slice(7).split('|').map(x=>`<option>${x}</option>`).join('')}</select></div>`;return `<div class="field"><label>${label}</label><input id="${id}"></div>`}).join('');
  if(type!=='lab_order'&&type!=='image_order')return `<div class="medical-form-section"><div class="section-title"><h3>${cfg.title}</h3></div>${standardFields}</div>`;
  const isImage=type==='image_order';
  const groups=isImage?COMMON_IMAGE_GROUPS:COMMON_LAB_GROUPS;
  const matrix=Object.entries(groups).map(([group,tests])=>`<section class="lab-group-card"><div class="lab-group-title">${group}</div><div class="lab-check-grid">${tests.map(test=>`<label class="lab-check"><input type="checkbox" class="${isImage?'js-common-image':'js-common-lab'}" data-group="${esc(group)}" value="${esc(test)}"><span>${test}</span></label>`).join('')}</div></section>`).join('');
  return `<div class="medical-form-section lab-order-section"><div class="section-title lab-order-title"><div><span class="eyebrow">ORDEN MÉDICA</span><h3>${cfg.title}</h3><p>${isImage?'Seleccione el estudio de imagen y complete la información clínica.':'Seleccione únicamente los estudios necesarios.'}</p></div></div><div class="lab-matrix">${matrix}</div><div class="lab-special-box"><div class="field"><label>${isImage?'Estudio de imagen no listado':'Exámenes Especiales'}</label><textarea id="${isImage?'medicalSpecialImages':'medicalSpecialLabs'}" rows="4" placeholder="${isImage?'Escriba aquí cualquier estudio o protocolo especial no incluido en la matriz.':'Escriba aquí estudios específicos, pruebas especiales o cualquier examen no incluido en la matriz.'}"></textarea></div></div><div class="lab-order-meta">${standardFields}</div></div>`;
}
function initMedicalDocumentBuilder(patient){
  const type=$('#medicalDocType'),fields=$('#medicalDocFields'),meta=$('#medicalPrescriptionMeta');if(!type||!fields||!meta)return;
  const draw=()=>{
    fields.innerHTML=medicalDocumentFieldsMarkup(type.value);
    meta.innerHTML=type.value==='prescription'?`<div class="medical-doc-grid prescription-patient-meta"><div class="field"><label>Edad</label><input id="medicalPatientAge" type="number" min="0" max="130" step="1" placeholder="Años"></div><div class="field"><label>Peso</label><div class="input-with-unit"><input id="medicalPatientWeight" type="number" min="0" step="0.1" placeholder="Ej. 72.5"><span>kg</span></div></div></div>`:'';
  };
  draw();type.onchange=draw;
  $('#previewMedicalDocBtn').onclick=()=>{const previewWindow=window.open('about:blank','_blank');generateMedicalPdf(patient,true,previewWindow);};
  $('#generateMedicalDocBtn').onclick=()=>generateMedicalPdf(patient,false);
}
async function assetDataUrl(path){
  const res=await fetch(path);if(!res.ok)throw new Error(`No se pudo cargar ${path}`);const blob=await res.blob();return await new Promise((resolve,reject)=>{const r=new FileReader();r.onload=()=>resolve(r.result);r.onerror=reject;r.readAsDataURL(blob)});
}
function medicalDocCode(){const d=new Date(),part=`${d.getFullYear()}${String(d.getMonth()+1).padStart(2,'0')}${String(d.getDate()).padStart(2,'0')}`;return `DBCM-${part}-${crypto.randomUUID().slice(0,8).toUpperCase()}`}
function collectMedicalDocData(){
  const type=$('#medicalDocType').value,cfg=MEDICAL_DOC_TYPES[type],values={};
  cfg.fields.forEach(([label],i)=>values[label]=($('#medicalField'+i)?.value||'').trim());
  const selectedLabs=type==='lab_order'?[...document.querySelectorAll('.js-common-lab:checked')].map(el=>({group:el.dataset.group,test:el.value})):[];
  const selectedImages=type==='image_order'?[...document.querySelectorAll('.js-common-image:checked')].map(el=>({group:el.dataset.group,test:el.value})):[];
  const specialLabs=type==='lab_order'?($('#medicalSpecialLabs')?.value||'').trim():'';
  const specialImages=type==='image_order'?($('#medicalSpecialImages')?.value||'').trim():'';
  return {
    type,
    cfg,
    values,
    patientId:($('#medicalPatientId')?.value||'').trim(),
    sex:$('#medicalPatientSex')?.value||'',
    age:type==='prescription'?($('#medicalPatientAge')?.value||'').trim():'',
    weight:type==='prescription'?($('#medicalPatientWeight')?.value||'').trim():'',
    selectedLabs,
    specialLabs,
    selectedImages,
    specialImages
  };
}
function normalizeMedicalPdfText(value){
  let text=String(value??'')
    .replace(/[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/g,'')
    .replace(/[\u00a0\u2000-\u200A\u202F\u205F\u3000]/g,' ')
    .replace(/\t/g,' ')
    .replace(/\r\n?/g,'\n');
  const chars='A-Za-zÁÉÍÓÚÜÑáéíóúüñ0-9';
  text=text.split('\n').map(rawLine=>{
    let line=rawLine.trim();
    if(!line)return '';

    // Algunos dictados llegan como: "P a c i e n t e   e s t a b l e".
    // Si la mayoría de los fragmentos son caracteres individuales, se conserva
    // el espacio doble como separación entre palabras y se elimina únicamente
    // el espacio simple insertado entre letras o cifras.
    const tokens=line.split(/ +/).filter(Boolean);
    const singleTokens=tokens.filter(token=>new RegExp(`^[${chars}]$`).test(token)).length;
    if(tokens.length>=6 && singleTokens/tokens.length>=0.6){
      const boundary='\uE000';
      line=line.replace(/ {2,}/g,boundary);
      let previous='';
      const betweenChars=new RegExp(`([${chars}]) ([${chars}])`,'g');
      while(previous!==line){
        previous=line;
        line=line.replace(betweenChars,'$1$2');
      }
      line=line.replace(new RegExp(boundary,'g'),' ');
    }

    // Corrige además palabras aisladas con letras separadas.
    const spacedWord=new RegExp(`(^|[^${chars}])((?:[${chars}]\\s+){3,}[${chars}])(?=$|[^${chars}])`,'g');
    let previous='';
    while(previous!==line){
      previous=line;
      line=line.replace(spacedWord,(match,prefix,letters)=>prefix+letters.replace(/\s+/g,''));
    }
    return line.replace(/[ \f\v]+/g,' ').trim();
  }).join('\n').trim();
  return text||'—';
}
function splitMedicalPdfText(doc,value,maxWidth){
  const text=normalizeMedicalPdfText(value);
  const paragraphs=text.split(/\n+/);
  const lines=[];
  paragraphs.forEach((paragraph,index)=>{
    const wrapped=doc.splitTextToSize(paragraph||' ',maxWidth);
    lines.push(...wrapped);
    if(index<paragraphs.length-1)lines.push('');
  });
  return lines.length?lines:['—'];
}
function addPdfWrapped(doc,text,x,y,maxWidth,lineHeight=14){const lines=splitMedicalPdfText(doc,text,maxWidth);if(typeof doc.setCharSpace==='function')doc.setCharSpace(0);doc.text(lines,x,y);return y+lines.length*lineHeight}

async function blobToDataUrl(blob){
  return await new Promise((resolve,reject)=>{
    const reader=new FileReader();
    reader.onload=()=>resolve(reader.result);
    reader.onerror=()=>reject(reader.error||new Error('No se pudo leer la imagen QR.'));
    reader.readAsDataURL(blob);
  });
}
async function getMedicalDocumentQr(text){
  try{
    if(window.QRCode&&typeof window.QRCode.toDataURL==='function'){
      return await window.QRCode.toDataURL(text,{margin:1,width:180,color:{dark:'#221735',light:'#ffffff'}});
    }
  }catch(err){console.warn('Generador QR local no disponible:',err)}
  try{
    const url='https://api.qrserver.com/v1/create-qr-code/?size=180x180&margin=1&data='+encodeURIComponent(text);
    const response=await fetch(url,{cache:'no-store'});
    if(!response.ok)throw new Error('Servicio QR no disponible.');
    return await blobToDataUrl(await response.blob());
  }catch(err){
    console.warn('No se pudo generar el QR externo:',err);
    return null;
  }
}


function medicalBusinessTitleDataUrl(text){
  const canvas=document.createElement('canvas');
  canvas.width=980;canvas.height=120;
  const ctx=canvas.getContext('2d');
  ctx.clearRect(0,0,canvas.width,canvas.height);
  const gradient=ctx.createLinearGradient(0,0,canvas.width,0);
  gradient.addColorStop(0,'#2c136f');
  gradient.addColorStop(.48,'#7137cf');
  gradient.addColorStop(1,'#ee168f');
  ctx.fillStyle=gradient;
  ctx.font='700 52px "Avenir Next", "Segoe UI", Arial, sans-serif';
  ctx.textBaseline='middle';
  ctx.fillText(text,0,62);
  return canvas.toDataURL('image/png');
}


async function buildMedicalPdf(patient,data,code){
  if(!window.jspdf?.jsPDF)throw new Error('No se pudo iniciar el generador de PDF.');
  const {jsPDF}=window.jspdf;const doc=new jsPDF({unit:'pt',format:'letter'});const W=612,H=792;const purple=[92,42,196],ink=[35,27,53];
  const [logo,signature,stamp]=await Promise.all([assetDataUrl('assets/doctor-letterhead-logo.png'),assetDataUrl('assets/doctor-signature.png'),assetDataUrl('assets/doctor-stamp.png')]);
  doc.setFillColor(255,255,255);doc.rect(0,0,W,H,'F');doc.addImage(logo,'PNG',42,34,72,72);
  const businessTitle=medicalBusinessTitleDataUrl(DOCTOR_PROFILE.business);doc.addImage(businessTitle,'PNG',128,31,330,40);doc.setTextColor(...ink);doc.setFontSize(10);doc.setFont('helvetica','normal');doc.text(`${DOCTOR_PROFILE.name} · ${DOCTOR_PROFILE.title} · Cód. ${DOCTOR_PROFILE.code}`,128,76);doc.text(`${DOCTOR_PROFILE.area} · Tel. ${DOCTOR_PROFILE.phone} · ${DOCTOR_PROFILE.email}`,128,91);doc.text(DOCTOR_PROFILE.web,128,106);
  doc.setDrawColor(...purple);doc.setLineWidth(1.3);doc.line(42,118,570,118);
  doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(18);doc.text(data.cfg.title.toUpperCase(),306,150,{align:'center'});
  doc.setTextColor(...ink);doc.setFontSize(10);doc.setFont('helvetica','bold');doc.text('Paciente:',42,180);doc.setFont('helvetica','normal');doc.text(patient.full_name||'Paciente',95,180);doc.setFont('helvetica','bold');doc.text('Fecha:',390,180);doc.setFont('helvetica','normal');doc.text(new Date().toLocaleDateString('es-CR'),430,180);
  let y=199;doc.setFont('helvetica','bold');doc.text('Correo:',42,y);doc.setFont('helvetica','normal');doc.text(patient.email||'No indicado',85,y);if(data.patientId){doc.setFont('helvetica','bold');doc.text('Identificación:',330,y);doc.setFont('helvetica','normal');doc.text(data.patientId,405,y)}
  y+=18;if(data.sex||data.type==='prescription'){
    let x=42;
    if(data.sex){doc.setFont('helvetica','bold');doc.text('Sexo:',x,y);doc.setFont('helvetica','normal');doc.text(data.sex||'No indicado',x+35,y);x=220;}
    if(data.type==='prescription'){
      doc.setFont('helvetica','bold');doc.text('Edad:',x,y);doc.setFont('helvetica','normal');doc.text(data.age?`${data.age} años`:'No indicada',x+34,y);
      doc.setFont('helvetica','bold');doc.text('Peso:',390,y);doc.setFont('helvetica','normal');doc.text(data.weight?`${data.weight} kg`:'No indicado',425,y);
    }
  }
  y+=28;
  let pageNumber=1;
  const contentBottom=708;
  const addContinuationPage=()=>{
    doc.addPage();pageNumber+=1;
    doc.setFillColor(255,255,255);doc.rect(0,0,W,H,'F');
    doc.addImage(logo,'PNG',42,28,44,44);
    doc.addImage(businessTitle,'PNG',98,30,270,33);
    doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(11);
    doc.text(`${data.cfg.title.toUpperCase()} · CONTINUACIÓN`,42,91);
    doc.setTextColor(...ink);doc.setFont('helvetica','normal');doc.setFontSize(8.5);
    doc.text(`${patient.full_name||'Paciente'} · Página ${pageNumber}`,570,91,{align:'right'});
    doc.setDrawColor(...purple);doc.setLineWidth(1);doc.line(42,102,570,102);
    y=126;
  };
  const ensureSpace=(needed=20)=>{if(y+needed>contentBottom)addContinuationPage();};
  const writeSection=(label,value)=>{
    ensureSpace(31);
    doc.setFont('helvetica','bold');doc.setFontSize(10);doc.setTextColor(...purple);doc.text(label.toUpperCase(),42,y);y+=15;

    // Aplicar primero la tipografía real del cuerpo y después calcular los saltos.
    // Antes se calculaban con la fuente anterior y algunas líneas terminaban
    // sobrepasando el margen derecho al imprimirse.
    doc.setTextColor(...ink);doc.setFont('helvetica','normal');doc.setFontSize(10.5);
    if(typeof doc.setCharSpace==='function')doc.setCharSpace(0);
    const lines=splitMedicalPdfText(doc,value,500);

    for(const lineText of lines){
      ensureSpace(18);
      // ensureSpace puede crear una página de continuación, cuyo encabezado
      // usa una fuente menor. Restauramos siempre la tipografía del cuerpo
      // antes de imprimir cada línea para conservar el mismo tamaño.
      doc.setTextColor(...ink);
      doc.setFont('helvetica','normal');
      doc.setFontSize(10.5);
      if(typeof doc.setCharSpace==='function')doc.setCharSpace(0);
      doc.text(lineText,42,y,{maxWidth:500});
      y+=14;
    }
    y+=14;
  };
  if(data.type==='lab_order'||data.type==='image_order'){
    const isImage=data.type==='image_order';
    const selected=isImage?(data.selectedImages||[]):(data.selectedLabs||[]);
    const special=isImage?data.specialImages:data.specialLabs;
    const grouped={};selected.forEach(x=>{(grouped[x.group]||(grouped[x.group]=[])).push(x.test)});
    ensureSpace(30);doc.setFont('helvetica','bold');doc.setFontSize(10);doc.setTextColor(...purple);doc.text(isImage?'ESTUDIOS DE IMAGEN SOLICITADOS':'EXÁMENES SOLICITADOS',42,y);y+=17;
    if(!Object.keys(grouped).length&&!special){ensureSpace(20);doc.setTextColor(...ink);doc.setFont('helvetica','normal');doc.text(isImage?'No se seleccionaron estudios de imagen.':'No se seleccionaron exámenes.',42,y);y+=20;}
    for(const [group,tests] of Object.entries(grouped)){
      ensureSpace(34);doc.setFillColor(247,244,252);doc.roundedRect(42,y-11,528,22,6,6,'F');doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(9.5);doc.text(group,52,y+3);y+=20;
      doc.setTextColor(...ink);doc.setFont('helvetica','normal');doc.setFontSize(10.2);
      for(const test of tests){const testLines=doc.splitTextToSize('• '+test,510);for(const testLine of testLines){ensureSpace(18);doc.text(testLine,56,y);y+=14;}}
      y+=5;
    }
    if(special)writeSection(isImage?'ESTUDIO ESPECIAL O PROTOCOLO':'EXÁMENES ESPECIALES',special);
    for(const [label] of data.cfg.fields)writeSection(label,data.values[label]||'—');
  }else{
    for(const [label] of data.cfg.fields)writeSection(label,data.values[label]||'—');
  }
  if(y+178>724)addContinuationPage();
  if(y<545)y=545;doc.setDrawColor(220,214,234);doc.line(42,y,570,y);y+=12;
  doc.addImage(signature,'PNG',55,y+18,122,82);doc.addImage(stamp,'PNG',190,y+22,210,76);
  doc.setFont('helvetica','bold');doc.setFontSize(9.5);doc.setTextColor(...ink);doc.text(DOCTOR_PROFILE.name,55,y+132);doc.setFont('helvetica','normal');doc.text(`${DOCTOR_PROFILE.title} · Cód. ${DOCTOR_PROFILE.code}`,55,y+145);
  const verifyUrl=`https://velvety-lamington-7866a6.netlify.app/?verify=${encodeURIComponent(code)}`;
  const qr=await getMedicalDocumentQr(verifyUrl);
  if(qr){
    doc.addImage(qr,'PNG',478,y+55,78,78);
    doc.setFontSize(7.5);doc.text('Verificar documento',517,y+140,{align:'center'});
  }else{
    doc.setFontSize(7.5);doc.setTextColor(100,92,115);
    doc.text('Verificación disponible con el código del documento',517,y+95,{align:'center',maxWidth:100});
  }
  doc.setFontSize(7);doc.setTextColor(100,92,115);doc.text(`Código: ${code}`,42,770);doc.text('Documento confidencial emitido electrónicamente por Doctor Benard Consulta Médica.',306,770,{align:'center'});
  return doc;
}
async function generateMedicalPdf(patient,previewOnly=false,previewWindow=null){
  const btn=previewOnly?$('#previewMedicalDocBtn'):$('#generateMedicalDocBtn');const data=collectMedicalDocData();const code=medicalDocCode();const original=btn.textContent;btn.disabled=true;btn.textContent=previewOnly?'Preparando…':'Generando…';
  try{const pdf=await buildMedicalPdf(patient,data,code);if(previewOnly){const blobUrl=URL.createObjectURL(pdf.output('blob'));if(previewWindow&&!previewWindow.closed){previewWindow.location.href=blobUrl;}else{const a=document.createElement('a');a.href=blobUrl;a.target='_blank';a.rel='noopener';a.click();}setTimeout(()=>URL.revokeObjectURL(blobUrl),60000);return;}
    const blob=pdf.output('blob');const safe=data.cfg.title.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-zA-Z0-9]+/g,'_');const path=`${patient.id}/generated/${Date.now()}-${safe}.pdf`;
    let {error}=await sb.storage.from('medical-documents').upload(path,blob,{contentType:'application/pdf',cacheControl:'3600',upsert:false});if(error)throw error;
    const payload={patient_id:patient.id,family_member_id:patient.family_member_id||activeAdminFamilyMemberId||null,doctor_id:session.user.id,document_code:code,document_type:data.cfg.documentType,title:data.cfg.title,content_json:data,status:'active',storage_path:path};
    ({error}=await sb.from('generated_medical_documents').insert(payload));if(error){await sb.storage.from('medical-documents').remove([path]);throw error;}
    ({error}=await sb.from('documents').insert({patient_id:patient.id,family_member_id:patient.family_member_id||activeAdminFamilyMemberId||null,uploaded_by:session.user.id,title:data.cfg.title,document_type:data.cfg.documentType,storage_path:path,reviewed:true}));if(error)throw error;
    await sb.from('notifications').insert({user_id:patient.id,title:'Nuevo documento médico',body:`Tiene disponible: ${data.cfg.title}`});const pushResult=await sendPatientPush(patient.id,'Nuevo documento médico',`Tiene disponible: ${data.cfg.title}`);toast(pushResult.ok?'PDF generado y notificación enviada.':`PDF generado, pero la alerta no se entregó: ${pushResult.error}`);openPatient(patient.id);
  }catch(err){console.error(err);toast(err.message||'No se pudo generar el documento.');}finally{btn.disabled=false;btn.textContent=original;}
}

window.adminUploadDoc=async id=>{const f=$('#adFile').files[0];if(!f)return toast('Seleccione un PDF.');const path=`${id}/${crypto.randomUUID()}-${f.name.replace(/[^a-zA-Z0-9._-]/g,'_')}`;let {error}=await sb.storage.from('medical-documents').upload(path,f,{contentType:f.type});if(error)return toast(error.message);({error}=await sb.from('documents').insert({patient_id:id,family_member_id:activeAdminFamilyMemberId,uploaded_by:session.user.id,title:$('#adTitle').value.trim()||f.name,document_type:$('#adType').value,storage_path:path,reviewed:true}));if(error)return toast(error.message);await sb.from('notifications').insert({user_id:id,title:'Nuevo documento médico',body:`Su médico agregó: ${$('#adTitle').value.trim()||f.name}`});const pushResult=await sendPatientPush(id,'Nuevo documento médico',`Su médico agregó: ${$('#adTitle').value.trim()||f.name}`);toast(pushResult.ok?'Documento enviado y notificación entregada.':`Documento enviado, pero la alerta no se entregó: ${pushResult.error}`);openPatient(id)}

async function reassignAdminDocument(documentId,title,currentPatientId){
  try{
    const [{data:patients,error:pErr},{data:families,error:fErr},{data:docRow,error:dErr}]=await Promise.all([
      sb.from('profiles').select('id,full_name,email').eq('role','patient').order('full_name'),
      sb.from('family_members').select('id,owner_user_id,full_name,relationship').eq('active',true).order('full_name'),
      sb.from('documents').select('*').eq('id',documentId).single()
    ]);
    if(pErr||fErr||dErr)throw (pErr||fErr||dErr);
    const options=[];
    (patients||[]).forEach(p=>{
      options.push({key:`patient:${p.id}`,patientId:p.id,familyId:null,label:`${p.full_name||p.email||'Paciente'} · Titular`});
      (families||[]).filter(f=>f.owner_user_id===p.id).forEach(f=>options.push({key:`family:${f.id}`,patientId:p.id,familyId:f.id,label:`${f.full_name} · ${f.relationship||'Familiar'} de ${p.full_name||p.email||'Paciente'}`}));
    });
    const existing=document.getElementById('documentRecipientDialog');if(existing)existing.remove();
    const dialog=document.createElement('dialog');dialog.id='documentRecipientDialog';dialog.className='document-recipient-dialog';
    dialog.innerHTML=`<form method="dialog" class="document-recipient-card"><div class="document-recipient-heading"><div><span class="eyebrow">CAMBIAR DESTINATARIO</span><h3>${esc(title||'Documento')}</h3></div><button class="dialog-close" value="cancel" aria-label="Cerrar">×</button></div><div class="alert-banner compact"><strong>Importante:</strong><br><span class="muted">Esta acción cambia el expediente donde aparece el archivo, pero no modifica el nombre escrito dentro del PDF. Si el documento contiene datos del paciente incorrecto, elimínelo y genere uno nuevo.</span></div><div class="field"><label>Nuevo destinatario</label><select id="documentNewRecipient"><option value="">Seleccione paciente o familiar</option>${options.map(o=>`<option value="${o.key}">${esc(o.label)}</option>`).join('')}</select></div><div class="document-recipient-actions"><button class="btn secondary" value="cancel">Cancelar</button><button type="button" class="btn primary" id="confirmDocumentRecipient">Guardar cambio</button></div></form>`;
    document.body.appendChild(dialog);dialog.showModal();
    dialog.addEventListener('close',()=>dialog.remove(),{once:true});
    dialog.querySelector('#confirmDocumentRecipient').onclick=async()=>{
      const selected=options.find(o=>o.key===dialog.querySelector('#documentNewRecipient').value);if(!selected)return toast('Seleccione el nuevo destinatario.');
      const btn=dialog.querySelector('#confirmDocumentRecipient');btn.disabled=true;btn.textContent='Guardando…';
      const payload={patient_id:selected.patientId,family_member_id:selected.familyId};
      const {error}=await sb.from('documents').update(payload).eq('id',documentId);if(error){btn.disabled=false;btn.textContent='Guardar cambio';return toast(error.message);}
      await sb.from('generated_medical_documents').update(payload).eq('storage_path',docRow.storage_path);
      await sb.from('notifications').insert({user_id:selected.patientId,title:'Nuevo documento médico',body:`Tiene disponible: ${title||docRow.title||'Documento médico'}`});
      await sendPatientPush(selected.patientId,'Nuevo documento médico',`Tiene disponible: ${title||docRow.title||'Documento médico'}`);
      dialog.close();toast('Destinatario actualizado correctamente.');openPatient(currentPatientId);
    };
  }catch(err){console.error(err);toast(err.message||'No se pudo cambiar el destinatario.');}
}

async function deleteAdminDocument(documentId,path,title,currentPatientId){
  if(!confirm(`¿Eliminar definitivamente “${title||'este documento'}” del expediente del paciente? Esta acción no se puede deshacer.`))return;
  try{
    const {data:docRow,error:readError}=await sb.from('documents').select('*').eq('id',documentId).single();if(readError)throw readError;
    if(path){const {error:storageError}=await sb.storage.from('medical-documents').remove([path]);if(storageError)throw storageError;}
    await sb.from('generated_medical_documents').delete().eq('storage_path',path);
    const {error}=await sb.from('documents').delete().eq('id',documentId);if(error)throw error;
    const bodies=[`Tiene disponible: ${title||docRow.title}`,`Su médico agregó: ${title||docRow.title}`];
    await sb.from('notifications').delete().eq('user_id',docRow.patient_id).in('body',bodies);
    toast('Documento eliminado del expediente.');openPatient(currentPatientId);
  }catch(err){console.error(err);toast(err.message||'No se pudo eliminar el documento.');}
}

function inactiveStatusLabel(status){return ({pending:'Pendiente de invitar',invited:'Invitación enviada',activated:'Cuenta activada'})[status]||status;}
function inactiveActivationUrl(token){return `${location.origin}${location.pathname}?activate=${encodeURIComponent(token)}`;}
function inactiveInvitationMessage(patient){
  const first=String(patient.full_name||'Paciente').trim().split(/\s+/)[0];
  return `Hola, ${first}.\n\nSu documentación médica ya se encuentra preparada.\n\nPara recibir su dictamen, receta, comprobante o resumen de consulta de forma segura y mantener sus documentos organizados, complete su acceso a BernardHealth siguiendo estos pasos:\n\n1. Abra el siguiente enlace:\n${inactiveActivationUrl(patient.invitation_token)}\n\n2. Complete su correo, identificación y contraseña.\n\n3. Instale BernardHealth siguiendo esta guía:\n${location.origin}/assets/como-instalar-bernardhealth.png\n\nCuando termine la activación, sus documentos aparecerán dentro de su cuenta.\n\nBernardHealth\nTu salud, conectada y organizada.`;
}
async function adminInactivePatients(){
  const {data,error}=await sb.from('inactive_patients').select('*').neq('status','activated').order('created_at',{ascending:false});
  if(error)throw error;
  const patients=data||[];
  main.innerHTML=`<section class="inactive-hero"><div><span class="eyebrow light">NUEVO MÓDULO</span><h2>Pacientes inactivos</h2><p>Prepare el perfil, genere documentación y envíe la invitación por WhatsApp. Al activar su cuenta, el paciente pasará automáticamente a Pacientes activos.</p></div><span class="inactive-hero-icon">${ICONS.patients}</span></section>
  <section class="inactive-create-card"><div class="inactive-create-heading"><span>${ICONS.patients}</span><div><small>PERFIL PRELIMINAR</small><h3>Registrar paciente inactivo</h3><p>Solo necesita el nombre y el celular.</p></div></div><form id="inactivePatientForm"><div class="form-grid"><div class="field"><label>Nombre completo</label><input id="inactiveName" required placeholder="Nombre del paciente"></div><div class="field"><label>Número de WhatsApp</label><input id="inactivePhone" required inputmode="tel" placeholder="Ej. 8888 8888"></div><div class="field field-full"><label>Nota opcional</label><input id="inactiveNotes" placeholder="Ej. Dictamen para licencia, consulta externa..."></div></div><button class="btn inactive-create-btn" type="submit"><span>＋</span> Crear perfil preliminar</button></form></section>
  <div class="section-title inactive-list-title"><div><span class="eyebrow">PENDIENTES DE ACTIVACIÓN</span><h2>Perfiles creados</h2></div><span class="section-note">${patients.length}</span></div><div class="field inactive-search"><input id="inactiveSearch" placeholder="Buscar por nombre o teléfono"></div><div id="inactivePatientList" class="inactive-patient-grid"></div>`;
  const draw=q=>{$('#inactivePatientList').innerHTML=patients.filter(x=>!q||`${x.full_name} ${x.whatsapp_phone}`.toLowerCase().includes(q.toLowerCase())).map(x=>`<article class="inactive-patient-card"><div class="inactive-avatar">${String(x.full_name||'P').trim().charAt(0).toUpperCase()}</div><div class="inactive-card-main"><span class="inactive-status ${x.status}">${inactiveStatusLabel(x.status)}</span><h3>${esc(x.full_name)}</h3><p>${esc(displayWhatsappPhone(x.whatsapp_phone))}</p><small>${esc(x.notes||'Perfil preliminar listo para documentación')}</small></div><div class="inactive-card-actions"><button class="btn primary" data-action="open-inactive-patient" data-id="${x.id}">Abrir perfil</button><button class="btn whatsapp-invite-btn" data-action="invite-inactive-patient" data-id="${x.id}">WhatsApp</button><button class="btn danger-soft" data-action="delete-inactive-patient" data-id="${x.id}" data-name="${esc(x.full_name)}">Eliminar</button></div></article>`).join('')||'<div class="empty polished">No hay pacientes inactivos registrados.</div>';};
  draw('');$('#inactiveSearch').oninput=e=>draw(e.target.value);
  $('#inactivePatientForm').onsubmit=async e=>{e.preventDefault();const full_name=$('#inactiveName').value.trim(),whatsapp_phone=normalizeWhatsappPhone($('#inactivePhone').value),notes=$('#inactiveNotes').value.trim();if(!full_name)return toast('Escriba el nombre del paciente.');if(whatsapp_phone.length<10)return toast('Escriba un WhatsApp válido.');const {error}=await sb.from('inactive_patients').insert({full_name,whatsapp_phone,notes:notes||null,created_by:session.user.id});if(error)return toast(error.message);toast('Perfil preliminar creado.');render();};
}
async function adminInactivePatientDetail(){
  if(!activeInactivePatientId){currentView='inactive-patients';return render();}
  const [{data:p,error},{data:docs,error:dErr}]=await Promise.all([sb.from('inactive_patients').select('*').eq('id',activeInactivePatientId).single(),sb.from('inactive_patient_documents').select('*').eq('inactive_patient_id',activeInactivePatientId).order('created_at',{ascending:false})]);
  if(error||dErr)throw(error||dErr);
  main.innerHTML=`<section class="inactive-detail-hero"><div class="inactive-avatar large">${String(p.full_name||'P').trim().charAt(0).toUpperCase()}</div><div><span class="eyebrow light">PERFIL PRELIMINAR</span><h2>${esc(p.full_name)}</h2><p>${esc(displayWhatsappPhone(p.whatsapp_phone))} · ${inactiveStatusLabel(p.status)}</p></div><button class="btn whatsapp-invite-btn" id="inactiveInviteTop">Enviar invitación</button></section>
  <section class="inactive-info-card"><div><small>DATOS DISPONIBLES</small><strong>${esc(p.full_name)}</strong><span>${esc(displayWhatsappPhone(p.whatsapp_phone))}</span></div><button class="btn secondary" id="editInactivePatientBtn">Editar datos</button></section>
  ${medicalDocumentBuilderMarkup(p).replace('Generar PDF y Enviar','Generar y enviar por WhatsApp').replace('El documento se guardará en el expediente y aparecerá en Documentos Recibidos del paciente.','El PDF quedará guardado en este perfil preliminar. Puede compartirlo por WhatsApp y, cuando el paciente active su cuenta, aparecerá automáticamente en Documentos.')}
  <section class="inactive-upload-card"><div><span>${ICONS.documents}</span><div><small>OTRA DOCUMENTACIÓN</small><h3>Adjuntar PDF existente</h3><p>Factura, comprobante u otro archivo preparado fuera del generador.</p></div></div><div class="form-grid"><div class="field"><label>Título</label><input id="inactiveUploadTitle" placeholder="Ej. Comprobante de pago"></div><div class="field"><label>PDF</label><input id="inactiveUploadFile" type="file" accept="application/pdf"></div></div><button class="btn primary" id="inactiveUploadBtn">Guardar PDF</button></section>
  <div class="section-title"><div><span class="eyebrow">DOCUMENTACIÓN PREPARADA</span><h2>Archivos del paciente</h2></div><span class="section-note">${(docs||[]).length}</span></div><div class="inactive-doc-list">${(docs||[]).length?(docs||[]).map(d=>`<article class="inactive-doc-card"><span class="inactive-doc-icon">${ICONS.documents}</span><div><strong>${esc(d.title)}</strong><small>${fmtDate(d.created_at)}</small></div><div class="inactive-doc-actions"><button class="btn secondary" data-action="open-inactive-doc" data-path="${esc(d.storage_path)}">Abrir</button><button class="btn whatsapp-invite-btn" data-action="share-inactive-doc" data-id="${d.id}" data-path="${esc(d.storage_path)}" data-title="${esc(d.title)}">Compartir</button><button class="btn danger-soft" data-action="delete-inactive-doc" data-id="${d.id}" data-path="${esc(d.storage_path)}">Eliminar</button></div></article>`).join(''):'<div class="empty polished">Todavía no hay documentos preparados.</div>'}</div>`;
  initInactiveMedicalDocumentBuilder(p);
  $('#inactiveInviteTop').onclick=()=>inviteInactivePatient(p.id);
  $('#editInactivePatientBtn').onclick=async()=>{const name=prompt('Nombre completo',p.full_name);if(name===null)return;const phone=prompt('WhatsApp',displayWhatsappPhone(p.whatsapp_phone));if(phone===null)return;const normalized=normalizeWhatsappPhone(phone);if(!name.trim()||normalized.length<10)return toast('Revise el nombre y el WhatsApp.');const {error}=await sb.from('inactive_patients').update({full_name:name.trim(),whatsapp_phone:normalized,updated_at:new Date().toISOString()}).eq('id',p.id);if(error)return toast(error.message);toast('Datos actualizados.');render();};
  $('#inactiveUploadBtn').onclick=()=>uploadInactiveDocument(p);
}
function initInactiveMedicalDocumentBuilder(patient){
  const type=$('#medicalDocType'),fields=$('#medicalDocFields'),meta=$('#medicalPrescriptionMeta');if(!type||!fields||!meta)return;
  const draw=()=>{fields.innerHTML=medicalDocumentFieldsMarkup(type.value);meta.innerHTML=type.value==='prescription'?`<div class="medical-doc-grid prescription-patient-meta"><div class="field"><label>Edad</label><input id="medicalPatientAge" type="number" min="0" max="130" step="1" placeholder="Años"></div><div class="field"><label>Peso</label><div class="input-with-unit"><input id="medicalPatientWeight" type="number" min="0" step="0.1" placeholder="Ej. 72.5"><span>kg</span></div></div></div>`:'';};draw();type.onchange=draw;
  $('#previewMedicalDocBtn').onclick=()=>{const w=window.open('about:blank','_blank');generateInactiveMedicalPdf(patient,true,w);};
  $('#generateMedicalDocBtn').onclick=()=>generateInactiveMedicalPdf(patient,false);
}
async function generateInactiveMedicalPdf(patient,previewOnly=false,previewWindow=null){
  const btn=previewOnly?$('#previewMedicalDocBtn'):$('#generateMedicalDocBtn'),data=collectMedicalDocData(),code=medicalDocCode(),original=btn.textContent;btn.disabled=true;btn.textContent=previewOnly?'Preparando…':'Generando…';
  try{const pdf=await buildMedicalPdf({...patient,email:'No registrado'},data,code);if(previewOnly){const url=URL.createObjectURL(pdf.output('blob'));if(previewWindow&&!previewWindow.closed)previewWindow.location.href=url;setTimeout(()=>URL.revokeObjectURL(url),60000);return;}
    const blob=pdf.output('blob'),safe=data.cfg.title.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-zA-Z0-9]+/g,'_'),path=`inactive/${patient.id}/${Date.now()}-${safe}.pdf`;
    let {error}=await sb.storage.from('medical-documents').upload(path,blob,{contentType:'application/pdf',cacheControl:'3600',upsert:false});if(error)throw error;
    ({error}=await sb.from('inactive_patient_documents').insert({inactive_patient_id:patient.id,doctor_id:session.user.id,document_code:code,document_type:data.cfg.documentType,title:data.cfg.title,content_json:data,storage_path:path}));if(error){await sb.storage.from('medical-documents').remove([path]);throw error;}
    toast('PDF generado y guardado. Abriendo opciones de envío…');await shareInactiveGeneratedBlob(patient,blob,`${safe}.pdf`,data.cfg.title);render();
  }catch(err){console.error(err);toast(err.message||'No se pudo generar el documento.');}finally{btn.disabled=false;btn.textContent=original;}
}
async function shareInactiveGeneratedBlob(patient,blob,filename,title){
  const file=new File([blob],filename,{type:'application/pdf'});
  if(navigator.canShare?.({files:[file]})){try{await navigator.share({title,files:[file],text:`Documentación médica de ${patient.full_name}`});return;}catch(e){if(e.name==='AbortError')return;}}
  const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=filename;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),30000);
  setTimeout(()=>window.open(`https://wa.me/${normalizeWhatsappPhone(patient.whatsapp_phone)}?text=${encodeURIComponent(`Hola, ${patient.full_name.split(/\s+/)[0]}. Le comparto su documentación médica. El PDF fue preparado y puede adjuntarse en este chat.\n\nTambién puede activar BernardHealth aquí:\n${inactiveActivationUrl(patient.invitation_token)}`)}`,'_blank','noopener'),500);
}
async function uploadInactiveDocument(patient){const f=$('#inactiveUploadFile').files[0],title=$('#inactiveUploadTitle').value.trim()||f?.name;if(!f)return toast('Seleccione un PDF.');const path=`inactive/${patient.id}/${Date.now()}-${f.name.replace(/[^a-zA-Z0-9._-]/g,'_')}`;let {error}=await sb.storage.from('medical-documents').upload(path,f,{contentType:'application/pdf'});if(error)return toast(error.message);({error}=await sb.from('inactive_patient_documents').insert({inactive_patient_id:patient.id,doctor_id:session.user.id,document_type:'other',title,storage_path:path}));if(error)return toast(error.message);toast('PDF guardado en el perfil preliminar.');render();}
async function inviteInactivePatient(id){const {data,error}=await sb.from('inactive_patients').select('*').eq('id',id).single();if(error)return toast(error.message);await sb.from('inactive_patients').update({status:'invited',invited_at:new Date().toISOString(),updated_at:new Date().toISOString()}).eq('id',id);window.open(`https://wa.me/${normalizeWhatsappPhone(data.whatsapp_phone)}?text=${encodeURIComponent(inactiveInvitationMessage(data))}`,'_blank','noopener');}
window.openInactivePatient=id=>{pushNavigationState();activeInactivePatientId=id;currentView='inactive-patient-detail';render();};
window.inviteInactivePatient=inviteInactivePatient;
window.deleteInactivePatient=async(id,name)=>{if(!confirm(`¿Eliminar el perfil preliminar de ${name}?`))return;const {data:docs}=await sb.from('inactive_patient_documents').select('storage_path').eq('inactive_patient_id',id);const paths=(docs||[]).map(x=>x.storage_path).filter(Boolean);if(paths.length)await sb.storage.from('medical-documents').remove(paths);const {error}=await sb.from('inactive_patients').delete().eq('id',id);if(error)return toast(error.message);toast('Perfil preliminar eliminado.');render();};
window.openInactiveDoc=async path=>openDoc(path);
window.shareInactiveDoc=async(id,path,title)=>{const {data:p}=await sb.from('inactive_patients').select('*').eq('id',activeInactivePatientId).single();const {data,error}=await sb.storage.from('medical-documents').download(path);if(error)return toast(error.message);await shareInactiveGeneratedBlob(p,data,`${title.replace(/[^a-zA-Z0-9]+/g,'_')}.pdf`,title);};
window.deleteInactiveDoc=async(id,path)=>{if(!confirm('¿Eliminar este documento?'))return;if(path)await sb.storage.from('medical-documents').remove([path]);const {error}=await sb.from('inactive_patient_documents').delete().eq('id',id);if(error)return toast(error.message);toast('Documento eliminado.');render();};

async function adminAgenda(){
  const [{data:appointments,error:aErr},{data:rules,error:rErr},{data:blocks,error:bErr}]=await Promise.all([
    sb.from('appointments').select('*,profiles!appointments_patient_id_fkey(full_name),family_members(full_name)').order('starts_at',{ascending:true}),
    sb.from('availability_rules').select('*').order('day_of_week').order('start_time'),
    sb.from('availability_blocks').select('*').gte('ends_at',new Date().toISOString()).order('starts_at')
  ]);
  if(aErr||rErr||bErr) throw (aErr||rErr||bErr);
  const nowTs=Date.now();
  const pending=(appointments||[]).filter(a=>a.status==='requested'&&new Date(a.starts_at).getTime()>=nowTs);
  const upcoming=(appointments||[]).filter(a=>a.status==='confirmed'&&new Date(a.starts_at).getTime()>=nowTs);
  const archived=(appointments||[]).filter(a=>(['completed','rejected','cancelled'].includes(a.status)||new Date(a.starts_at).getTime()<nowTs)&&!a.admin_archived_at).sort((a,b)=>new Date(b.starts_at)-new Date(a.starts_at));

  const todayIso=localDateISO();
  const today=new Date(`${todayIso}T12:00:00-06:00`);
  const day=today.getDay();
  const mondayShift=day===0?-6:1-day;
  const weekStart=new Date(today); weekStart.setDate(today.getDate()+mondayShift+(adminAgendaWeekOffset*7));
  const weekEnd=new Date(weekStart); weekEnd.setDate(weekStart.getDate()+7);
  const inWeek=a=>{const t=new Date(a.starts_at).getTime();return t>=weekStart.getTime()&&t<weekEnd.getTime()};
  const weekConfirmed=(appointments||[]).filter(a=>a.status==='confirmed'&&inWeek(a)&&new Date(a.starts_at).getTime()>=nowTs).sort((a,b)=>new Date(a.starts_at)-new Date(b.starts_at));
  const weekDays=Array.from({length:7},(_,i)=>{const d=new Date(weekStart);d.setDate(weekStart.getDate()+i);return d});
  const fmtWeekDay=d=>new Intl.DateTimeFormat('es-CR',{timeZone:'America/Costa_Rica',weekday:'long',day:'numeric',month:'short'}).format(d);
  const weekLabel=`${new Intl.DateTimeFormat('es-CR',{timeZone:'America/Costa_Rica',day:'numeric',month:'short'}).format(weekStart)} – ${new Intl.DateTimeFormat('es-CR',{timeZone:'America/Costa_Rica',day:'numeric',month:'short',year:'numeric'}).format(new Date(weekEnd.getTime()-86400000))}`;
  const weeklyAgenda=weekDays.map(d=>{
    const iso=new Intl.DateTimeFormat('en-CA',{timeZone:'America/Costa_Rica',year:'numeric',month:'2-digit',day:'2-digit'}).format(d);
    const items=weekConfirmed.filter(a=>localDateISO(new Date(a.starts_at))===iso);
    return `<section class="week-day-card ${iso===todayIso?'is-today':''}"><div class="week-day-heading"><strong>${esc(fmtWeekDay(d))}</strong><span>${items.length} cita${items.length===1?'':'s'}</span></div><div class="list compact-week-list">${items.length?items.map(a=>`<div class="item"><span class="time-badge">${formatSlotTime(a.starts_at)}</span><div class="item-main"><strong>${esc(personName(a.profiles?.full_name,a.family_members))}</strong><span class="muted">${esc(a.type)} · ${esc(a.reason||'Sin motivo registrado')}</span></div><span class="pill green">Confirmada</span></div>`).join(''):'<div class="empty compact-empty">Sin citas confirmadas.</div>'}</div></section>`;
  }).join('');

  main.innerHTML=`
    <div class="section-title"><h2>Agenda Médica</h2><span class="section-note">Vista semanal</span></div>
    <section class="card weekly-agenda-card">
      <div class="week-toolbar"><button class="btn secondary" data-action="agenda-week-prev">‹ Semana anterior</button><div><strong>${esc(weekLabel)}</strong><button class="week-today-link" data-action="agenda-week-today">Ir a esta semana</button></div><button class="btn secondary" data-action="agenda-week-next">Semana siguiente ›</button></div>
      <div class="weekly-agenda-grid">${weeklyAgenda}</div>
    </section>

    <div class="section-title"><h2>Solicitudes Pendientes</h2><span class="section-note">${pending.length}</span></div>
    <div class="list">${pending.length?pending.map(a=>`<div class="item"><div><strong>${esc(personName(a.profiles?.full_name,a.family_members))} · ${esc(a.type)}</strong><span class="muted">${fmtDate(a.starts_at)} · ${esc(a.reason||'Sin motivo registrado')}</span></div><div class="item-actions"><button class="btn primary" data-action="set-appt" data-id="${a.id}" data-status="confirmed" data-patient="${a.patient_id}">Confirmar</button><button class="btn danger" data-action="set-appt" data-id="${a.id}" data-status="rejected" data-patient="${a.patient_id}">Rechazar</button></div></div>`).join(''):'<div class="empty polished">No hay solicitudes pendientes.</div>'}</div>

    <details class="admin-agenda-panel">
      <summary><span>Crear Horario y ver Horarios Disponibles</span><b>${rules?.length||0}</b></summary>
      <div class="admin-agenda-panel-body">
        <div class="card"><h3>Crear Horario Semanal</h3><div class="form-grid">
          <div class="field field-full"><label>Días</label><div class="weekday-selector" id="ruleDays">
            <label class="weekday-option"><input type="checkbox" name="ruleDay" value="1"><span>Lunes</span></label><label class="weekday-option"><input type="checkbox" name="ruleDay" value="2"><span>Martes</span></label><label class="weekday-option"><input type="checkbox" name="ruleDay" value="3"><span>Miércoles</span></label><label class="weekday-option"><input type="checkbox" name="ruleDay" value="4"><span>Jueves</span></label><label class="weekday-option"><input type="checkbox" name="ruleDay" value="5"><span>Viernes</span></label><label class="weekday-option"><input type="checkbox" name="ruleDay" value="6"><span>Sábado</span></label><label class="weekday-option"><input type="checkbox" name="ruleDay" value="0"><span>Domingo</span></label>
          </div><small class="field-help">Seleccione todos los días que tendrán este mismo horario.</small></div>
          <div class="field"><label>Tipo de atención</label><select id="ruleType"><option>Consulta presencial</option><option>Cita virtual</option><option>Visita domiciliaria</option></select></div><div class="field"><label>Desde</label><input id="ruleStart" type="time" value="08:00"></div><div class="field"><label>Hasta</label><input id="ruleEnd" type="time" value="12:00"></div><div class="field"><label>Duración</label><select id="ruleMinutes"><option value="20">20 minutos</option><option value="30" selected>30 minutos</option><option value="40">40 minutos</option><option value="60">60 minutos</option></select></div>
        </div><button class="btn primary" data-action="create-availability-rule">Guardar horario</button></div>
        <div class="subsection-title"><span>Horarios disponibles configurados</span></div>
        <div class="list">${rules?.length?rules.map(x=>`<div class="item"><div><strong>${dayLabel(x.day_of_week)} · ${esc(x.appointment_type)}</strong><span class="muted">${String(x.start_time).slice(0,5)} a ${String(x.end_time).slice(0,5)} · ${x.slot_minutes} minutos</span></div><button class="btn danger" data-action="delete-availability-rule" data-id="${x.id}">Eliminar</button></div>`).join(''):'<div class="empty">Aún no ha configurado horarios semanales.</div>'}</div>
      </div>
    </details>

    <details class="emergency-block-panel">
      <summary>Bloquear Horario</summary>
      <div class="emergency-block-body"><p>Use esta opción cuando surja una emergencia, reunión o ausencia inesperada.</p><div class="form-grid"><div class="field"><label>Inicio</label><input id="blockStart" type="datetime-local"></div><div class="field"><label>Fin</label><input id="blockEnd" type="datetime-local"></div><div class="field"><label>Motivo</label><input id="blockReason" placeholder="Ej. emergencia, reunión o ausencia"></div></div><button class="btn danger full" data-action="create-availability-block">Bloquear Horario</button>
      <div class="subsection-title"><span>Bloqueos próximos</span></div><div class="list">${blocks?.length?blocks.map(x=>`<div class="item"><div><strong>${fmtDate(x.starts_at)} a ${formatSlotTime(x.ends_at)}</strong><span class="muted">${esc(x.reason||'Horario bloqueado')}</span></div><button class="btn danger" data-action="delete-availability-block" data-id="${x.id}">Eliminar</button></div>`).join(''):'<div class="empty">No hay bloqueos próximos.</div>'}</div></div>
    </details>

    <details class="archive-panel"><summary>Citas Cerradas o Canceladas <span>${archived.length}</span></summary><div class="archive-toolbar"><p>Se muestran hasta 60 registros recientes. Puede limpiar esta lista sin borrar el historial clínico de la base de datos.</p>${archived.length?'<button class="btn secondary archive-history-btn" data-action="archive-closed-appointments">Limpiar Historial Visible</button>':''}</div><div class="list archive-list">${archived.slice(0,60).map(a=>`<div class="item compact-item"><div><strong>${esc(personName(a.profiles?.full_name,a.family_members))} · ${esc(a.type)}</strong><span class="muted">${fmtDate(a.starts_at)} · ${esc(a.cancellation_reason?`Cancelación: ${a.cancellation_reason}`:(a.reason||'Sin motivo registrado'))}</span></div><span class="pill ${a.status==='completed'?'green':(a.status==='rejected'||a.status==='cancelled')?'red':'amber'}">${esc(statusLabel(a.status))}</span></div>`).join('')||'<div class="empty">No hay citas cerradas o canceladas.</div>'}</div></details>`;
}
window.changeAgendaWeek=async delta=>{adminAgendaWeekOffset+=delta;await render();};
window.resetAgendaWeek=async()=>{adminAgendaWeekOffset=0;await render();};
window.createAvailabilityRule=async()=>{
  const days=[...document.querySelectorAll('input[name="ruleDay"]:checked')].map(x=>Number(x.value));
  const appointmentType=$('#ruleType').value;
  const startTime=$('#ruleStart').value;
  const endTime=$('#ruleEnd').value;
  const slotMinutes=Number($('#ruleMinutes').value);
  if(!days.length)return toast('Seleccione al menos un día.');
  if(!startTime||!endTime)return toast('Seleccione la hora de inicio y finalización.');
  if(startTime>=endTime)return toast('La hora final debe ser posterior a la hora inicial.');
  const {data:existing,error:readError}=await sb.from('availability_rules').select('day_of_week,appointment_type,start_time,end_time,slot_minutes');
  if(readError)return toast(readError.message);
  const normalizeTime=value=>String(value||'').slice(0,5);
  const payloads=days.filter(day=>!(existing||[]).some(rule=>
    Number(rule.day_of_week)===day&&
    rule.appointment_type===appointmentType&&
    normalizeTime(rule.start_time)===normalizeTime(startTime)&&
    normalizeTime(rule.end_time)===normalizeTime(endTime)&&
    Number(rule.slot_minutes)===slotMinutes
  )).map(day=>({day_of_week:day,appointment_type:appointmentType,start_time:startTime,end_time:endTime,slot_minutes:slotMinutes,active:true}));
  if(!payloads.length)return toast('Ese horario ya estaba configurado en los días seleccionados.');
  const {error}=await sb.from('availability_rules').insert(payloads);
  if(error)return toast(error.message);
  const omitted=days.length-payloads.length;
  toast(`${payloads.length} ${payloads.length===1?'día guardado':'días guardados'}${omitted?` · ${omitted} ya existía${omitted===1?'':'n'}`:''}.`);
  render();
};
window.deleteAvailabilityRule=async id=>{if(!confirm('¿Eliminar este horario semanal?'))return;const {error}=await sb.from('availability_rules').delete().eq('id',id);if(error)return toast(error.message);toast('Horario eliminado.');render()};
window.createAvailabilityBlock=async()=>{const start=$('#blockStart').value,end=$('#blockEnd').value;if(!start||!end)return toast('Seleccione inicio y fin del bloqueo.');const {error}=await sb.from('availability_blocks').insert({starts_at:new Date(start).toISOString(),ends_at:new Date(end).toISOString(),reason:$('#blockReason').value.trim()});if(error)return toast(error.message);toast('Horario bloqueado.');render()};
window.deleteAvailabilityBlock=async id=>{if(!confirm('¿Eliminar este bloqueo?'))return;const {error}=await sb.from('availability_blocks').delete().eq('id',id);if(error)return toast(error.message);toast('Bloqueo eliminado.');render()};
window.setAppt=async(id,status,pid)=>{const {error}=await sb.from('appointments').update({status}).eq('id',id);if(error)return toast(error.message);await sb.from('notifications').insert({user_id:pid,title:`Cita ${status==='confirmed'?'confirmada':'rechazada'}`,body:status==='confirmed'?'Su cita fue confirmada. Revise la agenda.':'La solicitud fue rechazada. Por favor solicite otra fecha.'});toast('Cita actualizada.');render()}
async function adminInbox(){
  const [{data:d},{data:a},{data:r},{data:v},{data:cr},{data:cancelled},{data:adminNotifications}]=await Promise.all([
    sb.from('documents').select('*,profiles!documents_patient_id_fkey(full_name),family_members(full_name)').eq('reviewed',false).order('created_at',{ascending:false}),
    sb.from('appointments').select('*,profiles!appointments_patient_id_fkey(full_name),family_members(full_name)').eq('status','requested').gte('starts_at',new Date().toISOString()).order('starts_at',{ascending:true}),
    sb.from('health_records').select('*,profiles!health_records_patient_id_fkey(full_name),family_members(full_name)').is('alert_reviewed_at',null).is('doctor_hidden_at',null).order('created_at',{ascending:false}).limit(50),
    sb.from('virtual_contacts').select('*,profiles!virtual_contacts_patient_id_fkey(full_name,email),family_members(full_name)').in('status',['requested','approved','scheduled']).order('created_at',{ascending:false}),
    sb.from('coupon_redemptions').select('*,profiles!coupon_redemptions_patient_id_fkey(full_name,email),coupons(title,code,expires_at)').in('status',['requested','approved']).order('requested_at',{ascending:false}),
    sb.from('appointments').select('*,profiles!appointments_patient_id_fkey(full_name)').eq('status','cancelled').eq('cancelled_by','patient').is('cancellation_reviewed_at',null).order('cancelled_at',{ascending:false}).limit(50),
    sb.from('notifications').select('*').eq('user_id',session.user.id).eq('read',false).order('created_at',{ascending:false}).limit(50)
  ]);
  const alertsCount=(d?.length||0)+(r?.length||0)+(cancelled?.length||0)+(adminNotifications?.length||0);
  const actionCount=(a?.length||0)+(v?.length||0)+(cr?.length||0);
  const totalCount=alertsCount+actionCount;
  const alertRows=[];
  (adminNotifications||[]).forEach(x=>alertRows.push(`<div class="item"><span class="list-icon lilac">${ICONS.inbox}</span><div class="item-main"><strong>${esc(x.title)}</strong><span class="muted">${esc(x.body)} · ${fmtDate(x.created_at)}</span></div><button class="btn ghost" data-action="mark-read" data-id="${x.id}">Marcar leída</button></div>`));
  (cancelled||[]).forEach(x=>alertRows.push(`<div class="item cancellation-alert-item"><span class="alert-dot-red"></span><div class="item-main"><strong>${esc(personName(x.profiles?.full_name,x.family_members))} · Cita cancelada</strong><span class="muted">${fmtDate(x.starts_at)} · Motivo: ${esc(x.cancellation_reason||'No indicado')}</span></div><button class="btn secondary" data-action="archive-cancellation-alert" data-id="${x.id}">Marcar leída</button></div>`));
  (r||[]).forEach(x=>alertRows.push(`<div class="item"><span class="list-icon">${ICONS.controls}</span><div class="item-main"><strong>${esc(personName(x.profiles?.full_name,x.family_members))} · ${esc(labelType(x.record_type))}</strong><span class="muted">${esc(x.value_text||'')} · ${fmtDate(x.recorded_at)}</span></div><div class="item-actions"><button class="btn secondary" data-action="review-record" data-id="${x.id}">Marcar leída</button><button class="btn danger" data-action="hide-admin-control" data-id="${x.id}">Quitar</button></div></div>`));
  (d||[]).forEach(x=>alertRows.push(`<div class="item"><span class="list-icon">${ICONS.documents}</span><div class="item-main"><strong>${esc(personName(x.profiles?.full_name,x.family_members))} · ${esc(x.title)}</strong><span class="muted">Documento recibido · ${fmtDate(x.created_at)}</span></div><div class="item-actions"><button class="btn secondary" data-action="open-doc" data-path="${esc(x.storage_path)}">Abrir</button><button class="btn primary" data-action="review-doc" data-id="${x.id}">Marcar leída</button></div></div>`));
  main.innerHTML=`<div class="section-title counter-heading inbox-counter-heading"><div><span class="eyebrow">BANDEJA GENERAL</span><h2>Alertas</h2><p>Todos los avisos informativos aparecen juntos en una sola bandeja.</p></div><span class="counter-badge"><strong>${totalCount}</strong><small>pendiente${totalCount===1?'':'s'}</small></span></div>
    ${alertsCount?`<div class="alert-cleanup-bar"><div><strong>${alertsCount} alerta${alertsCount===1?'':'s'} sin leer</strong><span class="muted">Al marcarlas como leídas solo se cierran en esta bandeja; no se elimina ningún registro.</span></div><button class="btn primary" data-action="resolve-all-informational-alerts">Marcar todas como leídas</button></div>`:''}
    <div class="list notification-list unified-alert-list">${alertRows.length?alertRows.join(''):'<div class="empty polished alert-all-clear"><strong>Está al día</strong><span>No hay alertas informativas pendientes.</span></div>'}</div>
    <div class="section-title actions-heading counter-heading"><div><span class="eyebrow">REQUIEREN UNA DECISIÓN</span><h2>Solicitudes pendientes</h2><p>Estas solicitudes permanecen hasta que sean atendidas, aprobadas o rechazadas.</p></div><span class="counter-badge"><strong>${actionCount}</strong><small>solicitud${actionCount===1?'':'es'}</small></span></div>
    <div class="subsection-title"><span>Citas solicitadas</span></div><div class="list">${a?.length?a.map(x=>`<div class="item"><div><strong>${esc(personName(x.profiles?.full_name,x.family_members))} · ${esc(x.type)}</strong><span class="muted">${fmtDate(x.starts_at)} · ${esc(x.reason||'Sin motivo')}</span></div><div class="item-actions"><button class="btn primary" data-action="set-appt" data-id="${x.id}" data-status="confirmed" data-patient="${x.patient_id}">Confirmar</button><button class="btn danger" data-action="set-appt" data-id="${x.id}" data-status="rejected" data-patient="${x.patient_id}">Rechazar</button></div></div>`).join(''):'<div class="empty">Sin citas solicitadas.</div>'}</div>
    <div class="subsection-title"><span>Citas virtuales</span></div><div class="list">${v?.length?v.map(x=>`<div class="item"><div><strong>${esc(personName(x.profiles?.full_name,x.family_members)||x.profiles?.email)} · ${esc(virtualContactTypeLabel(x.request_type))}</strong><span class="muted">${esc(x.message||'Sin detalle')} · ${fmtDate(x.created_at)}</span></div><div class="item-actions">${x.status==='requested'?`<button class="btn secondary" data-action="set-virtual" data-id="${x.id}" data-status="approved">Aprobar</button>`:''}<button class="btn primary" data-action="set-virtual" data-id="${x.id}" data-status="used">Marcar atendido</button><button class="btn danger" data-action="set-virtual" data-id="${x.id}" data-status="rejected">Rechazar</button></div></div>`).join(''):'<div class="empty">Sin citas virtuales pendientes.</div>'}</div>
    <div class="subsection-title"><span>Cupones solicitados</span></div><div class="list">${cr?.length?cr.map(x=>`<div class="item"><div><strong>${esc(personName(x.profiles?.full_name,x.family_members)||x.profiles?.email)} · ${esc(x.coupons?.title||'Cupón')}</strong><span class="muted">Código ${esc(x.coupons?.code||'')} · ${esc(couponStatusLabel(x.status))}</span></div><div class="item-actions">${x.status==='requested'?`<button class="btn secondary" data-action="set-coupon" data-id="${x.id}" data-status="approved">Aprobar</button>`:''}<button class="btn primary" data-action="set-coupon" data-id="${x.id}" data-status="used">Marcar utilizado</button><button class="btn danger" data-action="set-coupon" data-id="${x.id}" data-status="rejected">Rechazar</button></div></div>`).join(''):'<div class="empty">Sin cupones solicitados.</div>'}</div>`;
}
window.markRead=async id=>{const {error}=await sb.from('notifications').update({read:true}).eq('id',id).eq('user_id',session.user.id);if(error)return toast(error.message);toast('Alerta vista.');await render();};
window.reviewRecord=async id=>{const now=new Date().toISOString();const {error}=await sb.from('health_records').update({alert_reviewed_at:now}).eq('id',id).is('alert_reviewed_at',null);if(error)return toast(error.message||'No se pudo marcar el control como leído.');toast('Control marcado como leído.');await render();};
window.hideAdminControl=async id=>{if(!confirm('¿Quitar este control de su panel? El paciente conservará su registro.'))return;const {error}=await sb.rpc('admin_hide_health_record',{p_record_id:id});if(error)return toast(error.message);toast('Control retirado de su panel.');await render();};
window.resolveAllInformationalAlerts=async()=>{
  if(!confirm('¿Resolver todas las alertas informativas visibles? Los controles, documentos y citas canceladas permanecerán guardados en sus módulos correspondientes.'))return;
  const now=new Date().toISOString();
  const results=await Promise.all([
    sb.from('documents').update({reviewed:true}).eq('reviewed',false),
    sb.from('health_records').update({alert_reviewed_at:now}).is('alert_reviewed_at',null),
    sb.from('appointments').update({cancellation_reviewed_at:now}).eq('status','cancelled').eq('cancelled_by','patient').is('cancellation_reviewed_at',null),
    sb.from('notifications').update({read:true}).eq('user_id',session.user.id).eq('read',false)
  ]);
  const failed=results.find(x=>x.error);
  if(failed)return toast(failed.error.message||'No se pudieron resolver todas las alertas.');
  toast('Alertas informativas resueltas. Los registros permanecen guardados.');
  await render();
};
window.archiveClosedAppointments=async()=>{
  if(!confirm('¿Limpiar del panel todas las citas cerradas o canceladas visibles? Los registros no se eliminarán; quedarán archivados en la base de datos.'))return;
  const now=new Date().toISOString();
  const {error}=await sb.from('appointments').update({admin_archived_at:now}).is('admin_archived_at',null).or(`status.in.(completed,rejected,cancelled),starts_at.lt.${now}`);
  if(error)return toast(error.message||'No se pudo limpiar el historial visible.');
  toast('Historial visible archivado correctamente.');
  await render();
};

window.reviewDoc=async id=>{await sb.from('documents').update({reviewed:true}).eq('id',id);render()}

const GTI_LOGIN_URL='https://www.facturaelectronica.cr/ActivarCuentas/Login';
function crc(value){return new Intl.NumberFormat('es-CR',{style:'currency',currency:'CRC',maximumFractionDigits:0}).format(Number(value||0));}
function billingPaymentLabel(value){return value==='sinpe'?'SINPE Móvil':'Efectivo';}
function billingLocationLabel(value){return ({studio_dent:'Studio Dent',home:'Domicilio',virtual:'Virtual',company:'Empresa',other:'Otro lugar'})[value]||value;}
function billingMonthRange(value){const [y,m]=String(value||billingMonth).split('-').map(Number);const start=new Date(Date.UTC(y,m-1,1,6,0,0));const end=new Date(Date.UTC(y,m,1,6,0,0));return {start:start.toISOString(),end:end.toISOString()};}
function billingMonthTitle(value){const [y,m]=String(value||billingMonth).split('-').map(Number);return new Intl.DateTimeFormat('es-CR',{month:'long',year:'numeric',timeZone:'America/Costa_Rica'}).format(new Date(Date.UTC(y,m-1,15))).replace(/^./,c=>c.toUpperCase());}
function billingPersonOptionValue(patientId,familyId){return familyId?`family:${familyId}`:`self:${patientId}`;}

async function buildPaymentReceiptPdf(record){
  if(!window.jspdf?.jsPDF)throw new Error('No se pudo iniciar el generador de PDF.');
  const {jsPDF}=window.jspdf;const doc=new jsPDF({unit:'pt',format:'letter'});const W=612,H=792;const purple=[92,42,196],fuchsia=[219,61,153],ink=[35,27,53],muted=[107,96,130],panel=[248,245,252],soft=[242,236,249],green=[32,137,96];
  const pdfMoney=v=>`CRC ${Math.round(Number(v||0)).toLocaleString('es-CR')}`;
  const logo=await assetDataUrl('assets/doctor-letterhead-logo.png');
  const title=medicalBusinessTitleDataUrl(DOCTOR_PROFILE.business);
  doc.setFillColor(255,255,255);doc.rect(0,0,W,H,'F');
  doc.setFillColor(248,244,253);doc.rect(0,0,W,126,'F');
  doc.setFillColor(...purple);doc.roundedRect(42,34,528,92,24,24,'F');
  doc.addImage(logo,'PNG',58,49,60,60);doc.addImage(title,'PNG',132,48,285,34);
  doc.setTextColor(255,255,255);doc.setFont('helvetica','normal');doc.setFontSize(10.5);doc.text(`${DOCTOR_PROFILE.name} · ${DOCTOR_PROFILE.title} · Cód. ${DOCTOR_PROFILE.code}`,132,94);doc.text(`${DOCTOR_PROFILE.area} · Tel. ${DOCTOR_PROFILE.phone} · ${DOCTOR_PROFILE.email}`,132,109);
  doc.setFillColor(255,255,255);doc.roundedRect(403,52,151,56,18,18,'F');
  doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(9);doc.text('COMPROBANTE INTERNO',478.5,72,{align:'center'});doc.setFontSize(11.5);doc.text(record.receipt_number,478.5,89,{align:'center'});

  doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(20);doc.text('COMPROBANTE DE PAGO DE SERVICIOS MÉDICOS',306,158,{align:'center'});
  doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(9.5);doc.text('Constancia interna de pago para control médico y administrativo.',306,175,{align:'center'});

  doc.setFillColor(...panel);doc.roundedRect(42,196,528,78,18,18,'F');
  const infoCol=(label,value,x,y)=>{doc.setTextColor(...muted);doc.setFont('helvetica','bold');doc.setFontSize(8.2);doc.text(label.toUpperCase(),x,y);doc.setTextColor(...ink);doc.setFont('helvetica','bold');doc.setFontSize(10.5);doc.text(String(value||'—'),x,y+17)};
  infoCol('Paciente',record.patient_name,60,220);infoCol('Identificación',record.patient_identification||'No registrada',240,220);infoCol('Fecha',new Date(record.service_date).toLocaleDateString('es-CR'),435,220);
  doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(8.5);doc.text(`Código de validación: ${record.receipt_code}`,60,254);

  doc.setFillColor(...soft);doc.roundedRect(42,294,334,230,18,18,'F');
  doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(12);doc.text('Detalle del servicio',60,321);
  let y=352;
  const row=(label,value,emph=false)=>{doc.setTextColor(...muted);doc.setFont('helvetica','bold');doc.setFontSize(9);doc.text(label,60,y);doc.setTextColor(...ink);doc.setFont('helvetica',emph?'bold':'normal');doc.setFontSize(emph?14:10.5);doc.text(String(value),358,y,{align:'right'});y+=32;};
  row('Servicio',record.service_name);row('Lugar de atención',billingLocationLabel(record.location_type));row('Método de pago',billingPaymentLabel(record.payment_method));row('Subtotal',pdfMoney(record.base_amount));row(`IVA (${Number(record.tax_rate)}%)`,pdfMoney(record.tax_amount));
  doc.setDrawColor(223,214,239);doc.line(60,y-12,358,y-12);row('TOTAL PAGADO',pdfMoney(record.total_amount),true);
  doc.setFillColor(234,248,241);doc.roundedRect(60,486,150,28,12,12,'F');doc.setTextColor(...green);doc.setFont('helvetica','bold');doc.setFontSize(9.5);doc.text('ESTADO: PAGADO',135,504,{align:'center'});

  doc.setFillColor(252,247,251);doc.roundedRect(392,294,178,230,18,18,'F');
  doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(12);doc.text('Resumen financiero',410,321);
  const metric=(label,val,top,accent=false)=>{doc.setFillColor(accent?247:255,accent?236:255,accent?248:255);doc.roundedRect(410,top,142,42,14,14,'F');doc.setTextColor(...muted);doc.setFont('helvetica','bold');doc.setFontSize(8.3);doc.text(label.toUpperCase(),424,top+15);doc.setTextColor(...(accent?purple:ink));doc.setFont('helvetica','bold');doc.setFontSize(12);doc.text(val,424,top+31)};
  metric('Subtotal',pdfMoney(record.base_amount),338);
  metric('IVA',pdfMoney(record.tax_amount),388);
  metric('Total',pdfMoney(record.total_amount),438,true);
  if(record.location_type==='studio_dent')metric('Studio Dent 40%',pdfMoney(record.studio_dent_share),488);

  const verifyUrl=`https://velvety-lamington-7866a6.netlify.app/?verify=${encodeURIComponent(record.receipt_code)}`;const qr=await getMedicalDocumentQr(verifyUrl);
  doc.setFillColor(249,247,252);doc.roundedRect(42,553,528,150,18,18,'F');
  doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(12);doc.text('Validación y observaciones',60,579);
  doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(9.2);doc.text('Documento interno de constancia de pago. No sustituye la factura electrónica oficial emitida ante el Ministerio de Hacienda.',60,602,{maxWidth:320});
  doc.text('Si requiere la factura electrónica oficial, utilice su proceso habitual en GTI.',60,628,{maxWidth:320});
  if(qr){doc.addImage(qr,'PNG',444,578,90,90);doc.setTextColor(...muted);doc.setFontSize(7.8);doc.text('Escanee para verificar el comprobante',489,680,{align:'center',maxWidth:110});}

  doc.setDrawColor(226,219,236);doc.line(42,728,570,728);
  doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(8);doc.text(`Generado el ${new Date().toLocaleString('es-CR')}`,42,748);doc.text('Doctor Benard Consulta Médica',570,748,{align:'right'});
  return doc;
}

async function saveBillingPayment(peopleMap,services){
  const person=peopleMap.get($('#billingPerson').value);if(!person)return toast('Seleccione al paciente atendido.');
  const service=services.find(x=>x.id===$('#billingService').value);if(!service)return toast('Seleccione el servicio brindado.');
  const isManual=!!service.manual_amount;
  const customServiceName=isManual?($('#billingCustomServiceName').value||'').trim():service.name;
  if(isManual&&!customServiceName)return toast('Escriba el nombre del servicio.');
  const base=isManual?Number($('#billingManualAmount').value||0):Number(service.base_amount||0);if(base<=0)return toast('Escriba un monto válido.');
  const taxRate=Number(service.tax_rate||0),tax=Math.round(base*taxRate/100),total=base+tax,location=$('#billingLocation').value;
  const studioShare=location==='studio_dent'?Math.round(base*.40):0,doctorShare=base-studioShare;
  const serviceDate=$('#billingDate').value?new Date($('#billingDate').value+'T12:00:00-06:00').toISOString():new Date().toISOString();
  const btn=$('#saveBillingPaymentBtn');btn.disabled=true;btn.textContent='Generando comprobante…';let created=null;
  try{
    const payload={doctor_id:session.user.id,patient_id:person.patientId,family_member_id:person.familyId||null,patient_name:person.name,patient_identification:($('#billingIdentification').value||person.identification||'').trim(),patient_email:person.email||'',service_id:service.id,service_key:service.service_key,service_name:customServiceName,service_date:serviceDate,location_type:location,base_amount:base,tax_rate:taxRate,tax_amount:tax,total_amount:total,payment_method:$('#billingPaymentMethod').value,payment_status:'paid',studio_dent_share:studioShare,doctor_share:doctorShare,notes:$('#billingNotes').value.trim()};
    const ins=await sb.from('billing_records').insert(payload).select('*').single();if(ins.error)throw ins.error;created=ins.data;
    const pdf=await buildPaymentReceiptPdf(created);const blob=pdf.output('blob');const path=`${person.patientId}/billing/${Date.now()}-${created.receipt_number}.pdf`;
    const up=await sb.storage.from('medical-documents').upload(path,blob,{contentType:'application/pdf',cacheControl:'3600',upsert:false});if(up.error)throw up.error;
    const upd=await sb.from('billing_records').update({storage_path:path,updated_at:new Date().toISOString()}).eq('id',created.id);if(upd.error)throw upd.error;
    const docIns=await sb.from('documents').insert({patient_id:person.patientId,family_member_id:person.familyId||null,uploaded_by:session.user.id,title:`Comprobante de pago ${created.receipt_number}`,document_type:'payment_receipt',storage_path:path,reviewed:true});if(docIns.error)throw docIns.error;
    await sb.from('notifications').insert({user_id:person.patientId,title:'Comprobante de pago disponible',body:`Se agregó el comprobante ${created.receipt_number} por ${crc(total)}.`});
    await sendPatientPush(person.patientId,'Comprobante de pago disponible',`Se agregó el comprobante ${created.receipt_number} por ${crc(total)}.`);
    toast('Pago registrado y comprobante enviado al paciente.');billingPrefillPatientId=person.patientId;billingPrefillFamilyMemberId=person.familyId||null;await render();
  }catch(err){if(created?.id&&!created.storage_path)await sb.from('billing_records').delete().eq('id',created.id);toast(err.message||'No se pudo generar el comprobante.');}
  finally{if(btn){btn.disabled=false;btn.textContent='Registrar Pago y Generar Comprobante';}}
}

function billingReportHeaderDataUrl(){
  const canvas=document.createElement('canvas');
  canvas.width=1400;canvas.height=260;
  const ctx=canvas.getContext('2d');
  const gradient=ctx.createLinearGradient(0,0,canvas.width,0);
  gradient.addColorStop(0,'#4f1aa8');
  gradient.addColorStop(.52,'#7a3cd0');
  gradient.addColorStop(1,'#e04b9f');
  ctx.fillStyle=gradient;ctx.fillRect(0,0,canvas.width,canvas.height);
  ctx.globalAlpha=.12;ctx.fillStyle='#ffffff';ctx.beginPath();ctx.arc(1240,20,230,0,Math.PI*2);ctx.fill();
  ctx.globalAlpha=.08;ctx.beginPath();ctx.arc(1080,220,180,0,Math.PI*2);ctx.fill();
  ctx.globalAlpha=1;
  return canvas.toDataURL('image/png');
}

async function buildBillingReportPdf(records,kind='internal'){
  const {jsPDF}=window.jspdf;const doc=new jsPDF({unit:'pt',format:'letter'});const W=612,H=792;const purple=[92,42,196],ink=[35,27,53],muted=[107,96,130],panel=[248,245,252],soft=[252,247,251],line=[226,219,236];const pdfMoney=v=>`CRC ${Math.round(Number(v||0)).toLocaleString('es-CR')}`;const month=billingMonthTitle(billingMonth);const studio=kind==='studio';const rows=studio?records.filter(r=>r.location_type==='studio_dent'):records;
  const totalBase=rows.reduce((a,r)=>a+Number(r.base_amount),0),totalTax=rows.reduce((a,r)=>a+Number(r.tax_amount),0),total=rows.reduce((a,r)=>a+Number(r.total_amount),0),studioTotal=rows.reduce((a,r)=>a+Number(r.studio_dent_share),0),doctorNet=totalBase-studioTotal;
  const title=studio?'INFORME MENSUAL PARA STUDIO DENT':'INFORME MENSUAL DE SERVICIOS MÉDICOS';
  const subtitle=studio?'Liquidación de servicios realizados en consultorio.':'Resumen administrativo para control interno del médico.';

  const [logo,headerGradient,businessTitle]=await Promise.all([assetDataUrl('assets/doctor-letterhead-logo.png'),Promise.resolve(billingReportHeaderDataUrl()),Promise.resolve(medicalBusinessTitleDataUrl(DOCTOR_PROFILE.business))]);
  const drawHeader=(page=1)=>{
    doc.setFillColor(255,255,255);doc.rect(0,0,W,H,'F');
    doc.addImage(headerGradient,'PNG',42,32,528,98);
    doc.addImage(logo,'PNG',58,47,60,60);
    doc.setFillColor(255,255,255);doc.roundedRect(128,47,286,33,14,14,'F');
    doc.addImage(businessTitle,'PNG',140,51,252,24);
    doc.setTextColor(255,255,255);doc.setFont('helvetica','bold');doc.setFontSize(14.2);doc.text(title,130,100);
    doc.setFontSize(9.1);doc.setFont('helvetica','normal');doc.text(subtitle,130,115);
    doc.setFillColor(255,255,255);doc.roundedRect(446,47,106,26,13,13,'F');
    doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(8.9);doc.text(month,499,64,{align:'center'});
    doc.setFillColor(255,255,255);doc.roundedRect(480,81,72,22,11,11,'F');
    doc.setFont('helvetica','normal');doc.setFontSize(8.5);doc.text(`Página ${page}`,516,95,{align:'center'});
  };
  const drawFooter=()=>{doc.setDrawColor(...line);doc.line(42,742,570,742);doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(8);doc.text(`Generado por BernardHealth · ${new Date().toLocaleString('es-CR')}`,42,760);doc.text('Doctor Benard Consulta Médica',570,760,{align:'right'});};
  drawHeader(1);

  const card=(x,y,w,h,label,val,sub='')=>{doc.setFillColor(...panel);doc.roundedRect(x,y,w,h,18,18,'F');doc.setTextColor(...muted);doc.setFont('helvetica','bold');doc.setFontSize(8.2);doc.text(label.toUpperCase(),x+16,y+18);doc.setTextColor(...ink);doc.setFont('helvetica','bold');doc.setFontSize(15);doc.text(val,x+16,y+39);if(sub){doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(8.1);doc.text(sub,x+16,y+56,{maxWidth:w-30});}};
  card(42,154,118,72,'Servicios',String(rows.length),studio?'Atenciones realizadas':'Registros del mes');
  card(176,154,118,72,'Base',pdfMoney(totalBase),studio?'Monto antes de IVA':'Ingresos antes de IVA');
  card(310,154,118,72,studio?'Studio Dent 40%':'IVA',studio?pdfMoney(studioTotal):pdfMoney(totalTax),studio?'Monto a transferir':'Control interno');
  card(444,154,126,72,studio?'Liquidación':'Total cobrado',studio?pdfMoney(studioTotal):pdfMoney(total),studio?'Pago total del período':`${pdfMoney(doctorNet)} neto médico`);
  if(!studio){card(42,238,252,64,'Pago a Studio Dent',pdfMoney(studioTotal),'40% calculado solo sobre la base');card(318,238,252,64,'Ingreso médico antes de IVA',pdfMoney(doctorNet),'No incluye el IVA recaudado');}
  else{doc.setFillColor(...soft);doc.roundedRect(42,238,528,64,18,18,'F');doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(10.5);doc.text('Criterio de cálculo',58,262);doc.setTextColor(...muted);doc.setFont('helvetica','normal');doc.setFontSize(9.2);doc.text('El 40% para Studio Dent se calcula exclusivamente sobre el monto del servicio antes del IVA.',58,282,{maxWidth:480});}

  let y=334;const startY=y;const rowHeight=28;
  const intCols={date:[42,96],patient:[96,210],ident:[210,298],service:[298,422],base:[422,470],iva:[470,518],total:[518,570]};
  const stdCols={date:[42,97],patient:[97,240],service:[240,424],base:[424,498],share:[498,570]};
  const drawTableHeader=()=>{
    doc.setFillColor(247,242,252);doc.roundedRect(42,y,528,28,12,12,'F');doc.setTextColor(...purple);doc.setFont('helvetica','bold');doc.setFontSize(8.3);
    if(studio){doc.text('Fecha',stdCols.date[0]+8,y+18);doc.text('Paciente',stdCols.patient[0]+8,y+18);doc.text('Servicio',stdCols.service[0]+8,y+18);doc.text('Base',stdCols.base[1]-8,y+18,{align:'right'});doc.text('40%',stdCols.share[1]-8,y+18,{align:'right'});}else{doc.text('Fecha',intCols.date[0]+8,y+18);doc.text('Paciente',intCols.patient[0]+8,y+18);doc.text('Identificación',intCols.ident[0]+8,y+18);doc.text('Servicio',intCols.service[0]+8,y+18);doc.text('Base',intCols.base[1]-8,y+18,{align:'right'});doc.text('IVA',intCols.iva[1]-8,y+18,{align:'right'});doc.text('Total',intCols.total[1]-8,y+18,{align:'right'});}y+=38;};
  drawTableHeader();
  doc.setFont('helvetica','normal');doc.setFontSize(8.4);
  const newPage=page=>{drawFooter();doc.addPage();drawHeader(page);y=startY;drawTableHeader();};
  let page=1;
  if(!rows.length){doc.setFillColor(...soft);doc.roundedRect(42,y,528,54,16,16,'F');doc.setTextColor(...muted);doc.setFont('helvetica','bold');doc.setFontSize(10.5);doc.text('No hay registros en este período.',306,y+31,{align:'center'});}else{
    rows.forEach((r,i)=>{
      const date=new Date(r.service_date).toLocaleDateString('es-CR');
      const patient=String(r.patient_name||'').slice(0,24);
      const ident=String(r.patient_identification||'Sin cédula').slice(0,15);
      const service=String(r.service_name||'').slice(0,28);
      if(y+rowHeight>724){page+=1;newPage(page);} 
      if(i%2===0){doc.setFillColor(252,250,254);doc.roundedRect(42,y-3,528,rowHeight,10,10,'F');}
      doc.setTextColor(...ink);
      if(studio){doc.text(date,stdCols.date[0]+8,y+16);doc.text(patient,stdCols.patient[0]+8,y+16);doc.text(service,stdCols.service[0]+8,y+16);doc.text(pdfMoney(r.base_amount),stdCols.base[1]-8,y+16,{align:'right'});doc.text(pdfMoney(r.studio_dent_share),stdCols.share[1]-8,y+16,{align:'right'});}else{doc.text(date,intCols.date[0]+8,y+16);doc.text(patient,intCols.patient[0]+8,y+16);doc.text(ident,intCols.ident[0]+8,y+16);doc.text(service,intCols.service[0]+8,y+16);doc.text(pdfMoney(r.base_amount),intCols.base[1]-8,y+16,{align:'right'});doc.text(pdfMoney(r.tax_amount),intCols.iva[1]-8,y+16,{align:'right'});doc.text(pdfMoney(r.total_amount),intCols.total[1]-8,y+16,{align:'right'});} 
      y+=rowHeight;
    });
  }
  drawFooter();
  return doc;
}

async function toggleGtiInvoiced(id,current){const payload={gti_invoiced:!current,gti_invoiced_at:!current?new Date().toISOString():null,updated_at:new Date().toISOString()};const {error}=await sb.from('billing_records').update(payload).eq('id',id);if(error)return toast(error.message);toast(!current?'Marcado como facturado en GTI.':'Marcado como pendiente de facturar.');render();}

async function archiveBillingRecord(id,restore=false){
  const {error}=await sb.from('billing_records').update({admin_archived_at:restore?null:new Date().toISOString(),updated_at:new Date().toISOString()}).eq('id',id);
  if(error)return toast(error.message);
  toast(restore?'Comprobante restaurado.':'Comprobante archivado.');
  render();
}
async function deleteBillingRecord(id,path,number){
  if(!confirm(`¿Eliminar definitivamente el comprobante ${number||''}? Se borrará también del panel del paciente y no contará en los reportes.`))return;
  if(path){
    const {error:storageError}=await sb.storage.from('medical-documents').remove([path]);
    if(storageError)console.warn('No se pudo eliminar el PDF del almacenamiento:',storageError);
    const {error:docError}=await sb.from('documents').delete().eq('storage_path',path);
    if(docError)return toast(docError.message);
  }
  const {error}=await sb.from('billing_records').delete().eq('id',id);
  if(error)return toast(error.message);
  toast('Comprobante eliminado definitivamente.');
  render();
}

async function adminBilling(){
  const range=billingMonthRange(billingMonth);
  const [servicesQ,profilesQ,familyQ,recordsQ]=await Promise.all([
    sb.from('billing_services').select('*').eq('active',true).order('sort_order'),
    sb.from('profiles').select('id,full_name,email,whatsapp_phone,identification').eq('role','patient').order('full_name'),
    sb.from('family_members').select('*').eq('active',true).order('full_name'),
    sb.from('billing_records').select('*').gte('service_date',range.start).lt('service_date',range.end).eq('status','active').order('service_date',{ascending:false})
  ]);
  const fatal=servicesQ.error||profilesQ.error||familyQ.error||recordsQ.error;
  if(fatal){main.innerHTML=`<section class="billing-hero"><div><span class="eyebrow light">FACTURACIÓN INTERNA</span><h2>Control de Servicios e Ingresos</h2><p>Primero ejecute el archivo SQL del módulo V6 en Supabase.</p></div><span>${ICONS.billing}</span></section><div class="alert-banner"><strong>No se pudo abrir el módulo.</strong><br><span class="muted">${esc(fatal.message)}</span></div>`;return;}
  const services=servicesQ.data||[],profiles=profilesQ.data||[],family=familyQ.data||[],records=recordsQ.data||[];const visibleRecords=records.filter(r=>!r.admin_archived_at),archivedRecords=records.filter(r=>!!r.admin_archived_at);
  const profileById=new Map(profiles.map(x=>[x.id,x]));const people=[];
  profiles.forEach(p=>people.push({key:`self:${p.id}`,patientId:p.id,familyId:null,name:p.full_name||'Paciente',identification:p.identification||'',email:p.email||'',label:`${p.full_name||'Paciente'} · Titular`}));
  family.forEach(f=>{const owner=profileById.get(f.owner_user_id);if(owner)people.push({key:`family:${f.id}`,patientId:f.owner_user_id,familyId:f.id,name:f.full_name,identification:f.identification||'',email:owner.email||'',label:`${f.full_name} · ${f.relationship||'Familiar'} (${owner.full_name})`});});
  people.sort((a,b)=>a.name.localeCompare(b.name,'es'));const peopleMap=new Map(people.map(x=>[x.key,x]));
  const prefillKey=billingPrefillFamilyMemberId?`family:${billingPrefillFamilyMemberId}`:(billingPrefillPatientId?`self:${billingPrefillPatientId}`:'');
  const totalBase=records.reduce((a,r)=>a+Number(r.base_amount),0),totalTax=records.reduce((a,r)=>a+Number(r.tax_amount),0),total=records.reduce((a,r)=>a+Number(r.total_amount),0),studio=records.reduce((a,r)=>a+Number(r.studio_dent_share),0),pendingGti=records.filter(r=>!r.gti_invoiced).length;
  const byService={};records.forEach(r=>{const k=r.service_name;byService[k]=(byService[k]||0)+1;});
  main.innerHTML=`
    <section class="billing-hero"><div><span class="eyebrow light">FACTURACIÓN INTERNA</span><h2>Control de Servicios e Ingresos</h2><p>Registre pagos, envíe comprobantes y genere los informes del mes sin alterar su facturación oficial en GTI.</p></div><span>${ICONS.billing}</span></section>
    <div class="billing-month-bar"><div><small>PERIODO DE TRABAJO</small><strong>${billingMonthTitle(billingMonth)}</strong></div><input id="billingMonth" type="month" value="${billingMonth}"><button class="btn gti-btn" id="openGtiBtn">Abrir GTI</button></div>
    <div class="billing-metrics"><article><small>Servicios pagados</small><strong>${records.length}</strong><span>${pendingGti} pendientes en GTI</span></article><article><small>Total cobrado</small><strong>${crc(total)}</strong><span>Incluye IVA</span></article><article><small>IVA registrado</small><strong>${crc(totalTax)}</strong><span>Control interno</span></article><article><small>Pago Studio Dent</small><strong>${crc(studio)}</strong><span>40% antes de IVA</span></article></div>
    <details class="billing-entry-panel" open><summary><span class="billing-entry-icon">${ICONS.billing}</span><span><strong>Registrar Servicio y Pago</strong><small>El comprobante se emitirá únicamente como pagado.</small></span><b>⌄</b></summary><form id="billingPaymentForm" class="billing-entry-body">
      <div class="form-grid"><div class="field field-full"><label>Paciente o Familiar Atendido</label><select id="billingPerson" required><option value="">Seleccione una persona</option>${people.map(x=>`<option value="${x.key}" ${x.key===prefillKey?'selected':''}>${esc(x.label)}</option>`).join('')}</select></div><div class="field"><label>Identificación</label><input id="billingIdentification" readonly placeholder="Se carga automáticamente"><small class="field-help">Tomada del perfil del paciente o familiar.</small></div><div class="field"><label>Fecha del Servicio</label><input id="billingDate" type="date" value="${localDateISO()}"></div><div class="field"><label>Servicio</label><select id="billingService">${services.map(x=>`<option value="${x.id}">${esc(x.name)} · ${x.manual_amount?'Monto manual':crc(x.base_amount)}</option>`).join('')}</select></div><div class="field hidden" id="billingCustomServiceNameField"><label>Nombre del Servicio</label><input id="billingCustomServiceName" placeholder="Ej. Servicios Médicos"></div><div class="field hidden" id="billingManualAmountField"><label>Monto antes de IVA</label><input id="billingManualAmount" type="number" min="1" step="1" inputmode="numeric" placeholder="Ej. 182000"></div><div class="field"><label>Lugar de Atención</label><select id="billingLocation"><option value="studio_dent">Studio Dent</option><option value="home">Domicilio</option><option value="virtual">Virtual</option><option value="company">Empresa</option><option value="other">Otro lugar</option></select></div><div class="field"><label>Método de Pago</label><select id="billingPaymentMethod"><option value="sinpe">SINPE Móvil</option><option value="cash">Efectivo</option></select></div><div class="field field-full"><label>Observación</label><input id="billingNotes" placeholder="Opcional"></div></div>
      <div class="billing-calculation" id="billingCalculation"></div><button class="btn primary full billing-save-btn" id="saveBillingPaymentBtn" type="submit">Registrar Pago y Generar Comprobante</button>
    </form></details>
    <div class="billing-report-actions"><button class="btn secondary" id="downloadBillingReportBtn">Descargar Informe Mensual</button><button class="btn studio-report-btn" id="downloadStudioReportBtn">Informe para Studio Dent</button></div>
    <div class="section-title"><h2>Resumen por Servicio</h2><span class="section-note">${records.length} registros</span></div><div class="billing-service-summary">${Object.keys(byService).length?Object.entries(byService).map(([name,count])=>`<article><strong>${count}</strong><span>${esc(name)}</span></article>`).join(''):'<div class="empty">Todavía no hay servicios registrados en este mes.</div>'}</div>
    <div class="section-title"><h2>Comprobantes del Mes</h2><span class="section-note">Base ${crc(totalBase)}</span></div><div class="billing-record-list">${visibleRecords.length?visibleRecords.map(r=>`<article class="billing-record-card"><div class="billing-record-date"><strong>${new Date(r.service_date).toLocaleDateString('es-CR',{day:'2-digit'})}</strong><span>${new Date(r.service_date).toLocaleDateString('es-CR',{month:'short'}).replace('.','')}</span></div><div class="billing-record-main"><small>${esc(r.receipt_number)}</small><strong>${esc(r.patient_name)}</strong><span>${esc(r.service_name)} · ${billingLocationLabel(r.location_type)} · ${billingPaymentLabel(r.payment_method)}</span></div><div class="billing-record-total"><strong>${crc(r.total_amount)}</strong><span class="pill ${r.gti_invoiced?'green':'amber'}">${r.gti_invoiced?'Facturado GTI':'Pendiente GTI'}</span></div><div class="billing-record-actions">${r.storage_path?`<button class="btn secondary js-open-billing-doc" data-path="${esc(r.storage_path)}">Abrir</button>`:''}<button class="btn ghost js-toggle-gti" data-id="${r.id}" data-current="${r.gti_invoiced}">${r.gti_invoiced?'Reabrir':'Marcar GTI'}</button><button class="btn receipt-archive-btn js-archive-billing" data-id="${r.id}">Archivar</button><button class="receipt-delete-btn js-delete-billing" title="Eliminar comprobante" aria-label="Eliminar comprobante" data-id="${r.id}" data-path="${esc(r.storage_path||'')}" data-number="${esc(r.receipt_number)}"><svg viewBox="0 0 24 24"><path d="M4 7h16"></path><path d="M9 7V4h6v3"></path><path d="M7 7l1 13h8l1-13"></path><path d="M10 11v5M14 11v5"></path></svg></button></div></article>`).join(''):'<div class="empty polished">No hay comprobantes visibles para este periodo.</div>'}</div>${archivedRecords.length?`<details class="billing-archive-panel"><summary><span>Comprobantes archivados</span><b>${archivedRecords.length}</b></summary><div class="billing-record-list billing-archive-list">${archivedRecords.map(r=>`<article class="billing-record-card is-archived"><div class="billing-record-date"><strong>${new Date(r.service_date).toLocaleDateString('es-CR',{day:'2-digit'})}</strong><span>${new Date(r.service_date).toLocaleDateString('es-CR',{month:'short'}).replace('.','')}</span></div><div class="billing-record-main"><small>${esc(r.receipt_number)}</small><strong>${esc(r.patient_name)}</strong><span>${esc(r.service_name)} · ${crc(r.total_amount)}</span></div><div class="billing-record-actions"><button class="btn secondary js-restore-billing" data-id="${r.id}">Restaurar</button><button class="receipt-delete-btn js-delete-billing" title="Eliminar definitivamente" aria-label="Eliminar definitivamente" data-id="${r.id}" data-path="${esc(r.storage_path||'')}" data-number="${esc(r.receipt_number)}"><svg viewBox="0 0 24 24"><path d="M4 7h16"></path><path d="M9 7V4h6v3"></path><path d="M7 7l1 13h8l1-13"></path><path d="M10 11v5M14 11v5"></path></svg></button></div></article>`).join('')}</div></details>`:''}`;
  const personSelect=$('#billingPerson'),ident=$('#billingIdentification'),serviceSelect=$('#billingService'),customNameField=$('#billingCustomServiceNameField'),customNameInput=$('#billingCustomServiceName'),manualField=$('#billingManualAmountField'),manualInput=$('#billingManualAmount'),calc=$('#billingCalculation');
  const syncPerson=()=>{const person=peopleMap.get(personSelect.value);ident.value=person?.identification||'';};
  const drawCalc=()=>{const service=services.find(x=>x.id===serviceSelect.value);const manual=!!service?.manual_amount;customNameField.classList.toggle('hidden',!manual);manualField.classList.toggle('hidden',!manual);const base=manual?Number(manualInput.value||0):Number(service?.base_amount||0);const tax=Math.round(base*Number(service?.tax_rate||0)/100),location=$('#billingLocation').value,studioShare=location==='studio_dent'?Math.round(base*.4):0;calc.innerHTML=`<div><small>Subtotal</small><strong>${crc(base)}</strong></div><div><small>IVA ${Number(service?.tax_rate||0)}%</small><strong>${crc(tax)}</strong></div><div><small>Total Pagado</small><strong>${crc(base+tax)}</strong></div><div><small>Studio Dent</small><strong>${crc(studioShare)}</strong></div>`;};
  syncPerson();drawCalc();personSelect.onchange=syncPerson;serviceSelect.onchange=drawCalc;customNameInput.oninput=drawCalc;manualInput.oninput=drawCalc;$('#billingLocation').onchange=drawCalc;$('#billingMonth').onchange=e=>{billingMonth=e.target.value;render();};$('#openGtiBtn').onclick=()=>window.open(GTI_LOGIN_URL,'_blank','noopener,noreferrer');$('#billingPaymentForm').onsubmit=e=>{e.preventDefault();saveBillingPayment(peopleMap,services);};$('#downloadBillingReportBtn').onclick=async()=>{const pdf=await buildBillingReportPdf(records,'internal');pdf.save(`Informe_Medico_${billingMonth}.pdf`);};$('#downloadStudioReportBtn').onclick=async()=>{const pdf=await buildBillingReportPdf(records,'studio');pdf.save(`Informe_Studio_Dent_${billingMonth}.pdf`);};document.querySelectorAll('.js-open-billing-doc').forEach(b=>b.onclick=()=>openDoc(b.dataset.path));document.querySelectorAll('.js-toggle-gti').forEach(b=>b.onclick=()=>toggleGtiInvoiced(b.dataset.id,b.dataset.current==='true'));document.querySelectorAll('.js-archive-billing').forEach(b=>b.onclick=()=>archiveBillingRecord(b.dataset.id,false));document.querySelectorAll('.js-restore-billing').forEach(b=>b.onclick=()=>archiveBillingRecord(b.dataset.id,true));document.querySelectorAll('.js-delete-billing').forEach(b=>b.onclick=()=>deleteBillingRecord(b.dataset.id,b.dataset.path,b.dataset.number));
}

async function adminMore(){
  const [{data:c},{data:camp},{data:ads},{data:benefits}]=await Promise.all([
    sb.from('coupons').select('*').order('created_at',{ascending:false}),
    sb.from('campaigns').select('*').order('created_at',{ascending:false}),
    sb.from('advertisements').select('*').order('created_at',{ascending:false}),
    sb.from('membership_benefits').select('*').order('sort_order',{ascending:true})
  ]);
  main.innerHTML=`
    <section class="management-hero"><div><span class="eyebrow light">GESTIÓN Y COMUNICACIÓN</span><h2>Contenido, beneficios y alertas</h2><p>Administre desde un solo lugar las comunicaciones y publicaciones de Medical Care.</p></div><span>${ICONS.benefits}</span></section><div class="management-access-grid"><button data-go="inbox"><span>${ICONS.inbox}</span><strong>Alertas y resultados</strong><small>Controles, documentos y solicitudes pendientes</small></button><button type="button" data-action="scroll-management-benefits"><span>${ICONS.benefits}</span><strong>Beneficios y cupones</strong><small>Servicios, campañas y publicidad</small></button></div>
    <div class="promo-admin-title"><span>${ICONS.benefits}</span><div><small>SERVICIOS</small><h2 id="benefitFormTitle">Crear Beneficio</h2><p>Publique beneficios permanentes con la misma presentación profesional de cupones y campañas.</p></div></div>
    <div class="card" id="benefitEditorCard"><div class="form-grid"><div class="field"><label>Título</label><input id="benTitle" placeholder="Ej. Visita domiciliaria preferencial"></div><div class="field"><label>Icono</label><select id="benIcon"><option value="benefits">Beneficio</option><option value="chat">Cita virtual</option><option value="agenda">Cita o visita</option><option value="documents">Documento</option><option value="controls">Salud o control</option></select></div><div class="field"><label>Posición en la lista</label><input id="benOrder" type="number" min="1" value="50"><small class="field-help">1 aparece primero, 2 aparece segundo, y así sucesivamente.</small></div></div><div class="field"><label>Descripción</label><textarea id="benDesc" placeholder="Explique claramente el beneficio y sus condiciones."></textarea></div><div class="item-actions"><button class="btn primary" id="saveBenefitBtn" data-action="create-benefit">Publicar beneficio</button><button class="btn secondary hidden" id="cancelBenefitEditBtn" data-action="cancel-benefit-edit">Cancelar edición</button></div></div>
    <div class="section-title"><h2>Beneficios Actuales</h2></div><div class="benefit-grid">${benefits?.length?benefits.map(x=>`<article class="benefit-card ${x.active?'':'is-disabled'}"><span class="benefit-card-icon">${benefitIcon(x.icon_key)}</span><div class="benefit-card-content"><strong>${esc(x.title)}</strong><p>${esc(x.description||'')}</p><div class="benefit-actions"><button class="btn secondary" data-action="edit-benefit" data-id="${x.id}">Editar</button><button class="btn secondary" data-action="toggle-benefit" data-id="${x.id}" data-active="${x.active}">${x.active?'Ocultar':'Activar'}</button><button class="btn danger" data-action="delete-benefit" data-id="${x.id}">Eliminar</button></div></div></article>`).join(''):'<div class="empty">Sin beneficios creados.</div>'}</div>
    <div class="promo-admin-title"><span>${ICONS.benefits}</span><div><small>PUBLICIDAD</small><h2>Crear cupón</h2><p>La imagen será la portada visible en el carrusel del paciente.</p></div></div>
    <div class="promo-editor-card"><div class="form-grid"><div class="field"><label>Título</label><input id="cpTitle"></div><div class="field"><label>Código</label><input id="cpCode"></div><div class="field field-full"><label>Descripción</label><textarea id="cpDesc" class="promo-description" placeholder="Escriba toda la información, condiciones y alcance del cupón."></textarea></div><div class="field"><label>Imagen promocional</label><input id="cpImage" type="file" accept="image/*"><small class="field-help">Obligatoria para mostrarlo en el inicio. Se optimiza y guarda dentro del cupón.</small></div><div class="field"><label>Vence</label><input id="cpEnd" type="date"></div><label class="featured-check"><input id="cpFeatured" type="checkbox" checked><span>Mostrar esta imagen en el inicio del paciente</span></label></div><div class="promo-editor-actions"><button class="btn primary" id="saveCouponBtn" data-action="save-coupon">Publicar cupón</button><button class="btn secondary hidden" id="cancelCouponBtn" data-action="cancel-coupon-edit">Cancelar edición</button></div></div>
    <div class="promo-admin-title campaign"><span>${ICONS.agenda}</span><div><small>COMUNICACIÓN</small><h2>Crear campaña</h2><p>Publique campañas preventivas, jornadas o promociones informativas.</p></div></div>
    <div class="promo-editor-card"><div class="form-grid"><div class="field"><label>Título</label><input id="camTitle"></div><div class="field field-full"><label>Descripción</label><textarea id="camDesc" class="promo-description"></textarea></div><div class="field"><label>Imagen o afiche</label><input id="camImage" type="file" accept="image/*"></div><div class="field"><label>Fecha de finalización</label><input id="camEnd" type="date"><small class="field-help">Se publica inmediatamente y desaparecerá automáticamente al finalizar esta fecha.</small></div><div class="field"><label>Destino del botón Solicitar</label><select id="camActionType"><option value="appointments">Solicitar cita</option><option value="coupons">Ir a cupones</option><option value="external">Enlace externo</option></select><small class="field-help">Al tocar el botón se cerrará la campaña y se abrirá directamente el destino seleccionado.</small></div><div class="field"><label>Enlace externo de la campaña</label><input id="camActionUrl" type="url" placeholder="https://..."><small class="field-help">Úselo cuando el destino sea WhatsApp, una página web, Facebook u otro sitio.</small></div><label class="featured-check"><input id="camFeatured" type="checkbox" checked><span>Mostrar esta imagen en el inicio del paciente</span></label></div><div class="promo-editor-actions"><button class="btn primary" id="saveCampaignBtn" data-action="save-campaign">Publicar campaña</button><button class="btn secondary hidden" id="cancelCampaignBtn" data-action="cancel-campaign-edit">Cancelar edición</button></div></div>
    <div class="promo-admin-title"><span>${ICONS.benefits}</span><div><small>ESPACIO COMERCIAL</small><h2>Crear publicidad</h2><p>Publique anuncios de empresas, comercios o profesionales y defina hasta cuándo estarán visibles.</p></div></div>
    <div class="promo-editor-card"><div class="form-grid"><div class="field"><label>Título o empresa</label><input id="adTitle" placeholder="Ej. Farmacia Salud Total"></div><div class="field"><label>Contacto visible</label><input id="adContact" placeholder="Ej. WhatsApp 8888-8888"></div><div class="field field-full"><label>Descripción</label><textarea id="adDesc" class="promo-description" placeholder="Describa el servicio, promoción o mensaje del anunciante."></textarea></div><div class="field"><label>Imagen publicitaria</label><input id="adImage" type="file" accept="image/*"><small class="field-help">La imagen se mostrará en el carrusel y en la sección de publicidad.</small></div><div class="field"><label>Enlace opcional</label><input id="adUrl" type="url" placeholder="https://..."></div><div class="field"><label>Fecha de finalización</label><input id="adEnd" type="date"><small class="field-help">El anuncio se publica inmediatamente y desaparece automáticamente al vencer.</small></div><label class="featured-check"><input id="adFeatured" type="checkbox" checked><span>Mostrar esta publicidad en el inicio del paciente</span></label></div><div class="promo-editor-actions"><button class="btn primary" id="saveAdBtn" data-action="save-advertisement">Publicar publicidad</button><button class="btn secondary hidden" id="cancelAdBtn" data-action="cancel-advertisement-edit">Cancelar edición</button></div></div>
    <div class="section-title"><h2>Cupones publicados</h2></div><div class="promo-admin-list">${c?.length?c.map(x=>promoAdminRow(x,'coupon')).join(''):'<div class="empty">Sin cupones.</div>'}</div>
    <div class="section-title"><h2>Publicidad publicada</h2></div><div class="promo-admin-list">${ads?.length?ads.map(x=>promoAdminRow(x,'advertisement')).join(''):'<div class="empty">Sin publicidad.</div>'}</div>
    <div class="section-title"><h2>Campañas publicadas</h2></div><div class="promo-admin-list">${camp?.length?camp.map(x=>promoAdminRow(x,'campaign')).join(''):'<div class="empty">Sin campañas.</div>'}</div>`;
}

window.saveSettings=async()=>{
  const payload={id:1,plan_name:$('#setPlanName').value.trim()||'Acceso Preferencial',membership_price:$('#setPrice').value===''?null:Number($('#setPrice').value),virtual_contacts_per_month:Number($('#setContacts').value||0),plan_description:$('#setDescription').value.trim(),updated_at:new Date().toISOString()};
  const {error}=await sb.from('app_settings').upsert(payload,{onConflict:'id'});if(error)return toast(error.message);appSettings={...appSettings,...payload};toast('Configuración guardada.');render();
};
window.createBenefit=async()=>{
  const title=$('#benTitle').value.trim();if(!title)return toast('Escriba el título del beneficio.');
  const {error}=await sb.from('membership_benefits').insert({title,description:$('#benDesc').value.trim(),icon_key:$('#benIcon').value,sort_order:Number($('#benOrder').value||0),active:true});if(error)return toast(error.message);toast('Beneficio publicado.');render();
};
window.editBenefit=async id=>{
  const {data,error}=await sb.from('membership_benefits').select('*').eq('id',id).single();
  if(error||!data)return toast(error?.message||'No se pudo abrir el beneficio.');
  editingBenefitId=id;
  $('#benTitle').value=data.title||'';
  $('#benDesc').value=data.description||'';
  $('#benIcon').value=data.icon_key||'benefits';
  $('#benOrder').value=Number(data.sort_order||50);
  $('#benefitFormTitle').textContent='Editar beneficio';
  const saveBtn=$('#saveBenefitBtn');saveBtn.textContent='Guardar cambios';saveBtn.dataset.action='update-benefit';
  $('#cancelBenefitEditBtn').classList.remove('hidden');
  $('#benefitEditorCard')?.scrollIntoView({behavior:'smooth',block:'start'});
  setTimeout(()=>$('#benTitle')?.focus(),350);
};
window.updateBenefit=async()=>{
  if(!editingBenefitId)return toast('Seleccione un beneficio para editar.');
  const title=$('#benTitle').value.trim();if(!title)return toast('Escriba el título del beneficio.');
  const payload={title,description:$('#benDesc').value.trim(),icon_key:$('#benIcon').value,sort_order:Number($('#benOrder').value||0),updated_at:new Date().toISOString()};
  const {error}=await sb.from('membership_benefits').update(payload).eq('id',editingBenefitId);
  if(error)return toast(error.message);
  editingBenefitId=null;toast('Beneficio actualizado.');render();
};
window.cancelBenefitEdit=()=>{
  editingBenefitId=null;
  $('#benTitle').value='';$('#benDesc').value='';$('#benIcon').value='benefits';$('#benOrder').value='50';
  $('#benefitFormTitle').textContent='Crear Beneficio';
  const saveBtn=$('#saveBenefitBtn');saveBtn.textContent='Publicar beneficio';saveBtn.dataset.action='create-benefit';
  $('#cancelBenefitEditBtn').classList.add('hidden');
};
window.toggleBenefit=async(id,active)=>{const {error}=await sb.from('membership_benefits').update({active:!active,updated_at:new Date().toISOString()}).eq('id',id);if(error)return toast(error.message);render();};
window.deleteBenefit=async id=>{if(!confirm('¿Eliminar este beneficio?'))return;const {error}=await sb.from('membership_benefits').delete().eq('id',id);if(error)return toast(error.message);toast('Beneficio eliminado.');render();};
async function promoImageDataUrl(file){
  if(!file)return null;
  if(!file.type?.startsWith('image/'))throw new Error('Seleccione un archivo de imagen válido.');
  if(file.size>8*1024*1024)throw new Error('La imagen no debe superar 8 MB.');
  const bitmap=await createImageBitmap(file),max=1400,scale=Math.min(1,max/bitmap.width),canvas=document.createElement('canvas');
  canvas.width=Math.max(1,Math.round(bitmap.width*scale));canvas.height=Math.max(1,Math.round(bitmap.height*scale));
  const ctx=canvas.getContext('2d',{alpha:false});ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.drawImage(bitmap,0,0,canvas.width,canvas.height);bitmap.close?.();
  return canvas.toDataURL('image/jpeg',.86);
}
function promoAdminRow(x,type){
  const isCoupon=type==='coupon',isAd=type==='advertisement',label=isCoupon?'Cupón':isAd?'Publicidad':'Campaña',dateText=isCoupon?`Vence ${fmtDate(x.expires_at)}`:`Finaliza ${fmtDate(x.ends_at)}`;
  return `<article class="promo-admin-row ${x.active?'':'is-hidden'}"><div class="promo-admin-thumb">${x.image_url?`<img src="${esc(x.image_url)}" alt="">`:`<span>Sin imagen</span>`}</div><div class="promo-admin-main"><small>${label} · ${x.active?'Publicado':'Oculto'}${x.featured?' · En inicio':''}</small><strong>${esc(x.title)}</strong><span>${dateText}${isCoupon?` · ${esc(x.code)}`:''}</span></div><div class="promo-admin-actions"><button class="btn secondary" data-action="edit-${type}" data-id="${x.id}">Editar</button><button class="btn secondary" data-action="toggle-${type}" data-id="${x.id}" data-active="${x.active}">${x.active?'Ocultar':'Publicar'}</button><button class="btn secondary" data-action="feature-${type}" data-id="${x.id}" data-featured="${!!x.featured}">${x.featured?'Quitar de inicio':'Mostrar en inicio'}</button><button class="btn danger" data-action="delete-${type}" data-id="${x.id}">Eliminar</button></div></article>`;
}
function setupPromotionCarousel(){
  const carousel=document.getElementById('promotionCarousel');if(!carousel)return;
  const slides=[...carousel.querySelectorAll('.promotion-slide')],dots=[...document.querySelectorAll('[data-promo-dot]')];if(slides.length<2)return;
  let index=0,timer;
  const go=i=>{index=(i+slides.length)%slides.length;carousel.scrollTo({left:slides[index].offsetLeft,behavior:'smooth'});dots.forEach((d,n)=>d.classList.toggle('active',n===index));};
  const start=()=>{clearInterval(timer);timer=setInterval(()=>go(index+1),5200)};dots.forEach((d,i)=>d.onclick=()=>{go(i);start()});carousel.addEventListener('scroll',()=>{const nearest=slides.reduce((best,s,i)=>Math.abs(s.offsetLeft-carousel.scrollLeft)<best.dist?{dist:Math.abs(s.offsetLeft-carousel.scrollLeft),i}:best,{dist:Infinity,i:0});index=nearest.i;dots.forEach((d,n)=>d.classList.toggle('active',n===index));},{passive:true});carousel.addEventListener('pointerdown',()=>clearInterval(timer));carousel.addEventListener('pointerup',start);start();
}
function openPromotionDetail(type,id){
  const x=promotionDetailCache.get(`${type}:${id}`);if(!x)return toast('No se pudo abrir esta promoción.');
  const existing=document.getElementById('promotionDetailModal');existing?.remove();
  const isCoupon=type==='coupon',isAd=type==='advertisement';const redemption=isCoupon?promotionRedemptionCache.get(id):null;
  const couponAction=!isCoupon?'':redemption&&['requested','approved','used'].includes(redemption.status)?`<div class="coupon-state-box"><strong>${esc(couponStatusLabel(redemption.status))}</strong><span>${redemption.status==='approved'?'Su cupón ya fue aprobado y no puede solicitarse otra vez.':redemption.status==='used'?'Este cupón ya fue utilizado.':'Su solicitud ya fue enviada.'}</span></div>`:`<button class="btn primary full" data-action="request-coupon" data-id="${x.id}">Solicitar cupón</button>`;
  const modal=document.createElement('div');modal.id='promotionDetailModal';modal.className='promotion-modal';modal.innerHTML=`<div class="promotion-modal-card"><button class="promotion-modal-close" aria-label="Cerrar">×</button>${x.image_url?`<img class="promotion-modal-image" src="${esc(x.image_url)}" alt="">`:''}<div class="promotion-modal-body"><span class="promo-label">${isCoupon?'Cupón':isAd?'Publicidad':'Campaña'}</span><h2>${esc(x.title)}</h2><p>${esc(x.description||'')}</p>${isCoupon?`<div class="coupon-code">${esc(x.code||'')}</div><small>Válido hasta ${fmtDate(x.expires_at)}</small>${couponAction}`:isAd?`${x.contact_text?`<strong>${esc(x.contact_text)}</strong>`:''}<small>Disponible hasta ${fmtDate(x.ends_at)}</small>${x.action_url?`<button class="btn primary full" data-action="open-ad-link" data-url="${esc(x.action_url)}">Más información</button>`:''}`:`<small>Disponible hasta ${fmtDate(x.ends_at)}</small><button class="btn primary full" data-action="campaign-cta" data-destination="${x.action_type==='external'?'external':x.action_type==='coupons'?'coupons':'appointments'}" data-url="${esc(x.action_url||'')}">${x.action_type==='external'?'Más información':x.action_type==='coupons'?'Ver cupones':'Solicitar cita'}</button>`}</div></div>`;document.body.appendChild(modal);modal.querySelector('.promotion-modal-close').onclick=()=>modal.remove();modal.onclick=e=>{if(e.target===modal)modal.remove()};
}
window.saveCoupon=async()=>{
  const title=$('#cpTitle').value.trim(),description=$('#cpDesc').value.trim(),code=$('#cpCode').value.trim(),end=$('#cpEnd').value,featured=$('#cpFeatured').checked,file=$('#cpImage')?.files?.[0];
  if(!title||!code||!end)return toast('Complete título, código y fecha de vencimiento.');
  try{const image=await promoImageDataUrl(file);if(featured&&!image&&!editingCouponId)return toast('Seleccione una imagen para mostrar el cupón en el inicio.');const payload={title,description,code,expires_at:new Date(end+'T23:59:59').toISOString(),active:true,featured,updated_at:new Date().toISOString()};if(image)payload.image_url=image;let q=editingCouponId?sb.from('coupons').update(payload).eq('id',editingCouponId):sb.from('coupons').insert(payload);const {error}=await q;if(error)throw error;editingCouponId=null;toast('Cupón guardado.');render();}catch(error){toast(error.message||'No se pudo guardar el cupón.');}
};
window.editCoupon=async id=>{const {data,error}=await sb.from('coupons').select('*').eq('id',id).single();if(error)return toast(error.message);editingCouponId=id;$('#cpTitle').value=data.title||'';$('#cpDesc').value=data.description||'';$('#cpCode').value=data.code||'';$('#cpEnd').value=(data.expires_at||'').slice(0,10);$('#cpFeatured').checked=!!data.featured;$('#saveCouponBtn').textContent='Guardar cambios';$('#cancelCouponBtn').classList.remove('hidden');$('#cpTitle').scrollIntoView({behavior:'smooth',block:'center'});};
window.cancelCouponEdit=()=>{editingCouponId=null;render();};
window.saveCampaign=async()=>{const title=$('#camTitle').value.trim(),description=$('#camDesc').value.trim(),end=$('#camEnd').value,action_type=$('#camActionType')?.value||'appointments',action_url=$('#camActionUrl')?.value.trim()||null,featured=$('#camFeatured').checked,file=$('#camImage')?.files?.[0];if(!title||!end)return toast('Complete el título y la fecha de finalización.');if(action_type==='external'&&!action_url)return toast('Escriba el enlace externo de la campaña.');if(action_url&&!/^https?:\/\//i.test(action_url))return toast('El enlace debe iniciar con http:// o https://');if(new Date(end+'T23:59:59').getTime()<Date.now())return toast('La fecha final debe ser hoy o una fecha futura.');try{const image=await promoImageDataUrl(file);if(featured&&!image&&!editingCampaignId)return toast('Seleccione una imagen para mostrar la campaña en el inicio.');const payload={title,description,action_type,action_url:action_type==='external'?action_url:null,starts_at:new Date().toISOString(),ends_at:new Date(end+'T23:59:59').toISOString(),active:true,featured,updated_at:new Date().toISOString()};if(image)payload.image_url=image;let q=editingCampaignId?sb.from('campaigns').update(payload).eq('id',editingCampaignId):sb.from('campaigns').insert(payload);const {error}=await q;if(error)throw error;editingCampaignId=null;toast('Campaña publicada inmediatamente. Se retirará automáticamente al vencer.');render();}catch(error){toast(error.message||'No se pudo guardar la campaña.');}};
window.editCampaign=async id=>{const {data,error}=await sb.from('campaigns').select('*').eq('id',id).single();if(error)return toast(error.message);editingCampaignId=id;$('#camTitle').value=data.title||'';$('#camDesc').value=data.description||'';$('#camEnd').value=(data.ends_at||'').slice(0,10);if($('#camActionType'))$('#camActionType').value=['appointments','coupons','external'].includes(data.action_type)?data.action_type:'appointments';if($('#camActionUrl'))$('#camActionUrl').value=data.action_url||'';$('#camFeatured').checked=!!data.featured;$('#saveCampaignBtn').textContent='Guardar cambios';$('#cancelCampaignBtn').classList.remove('hidden');$('#camTitle').scrollIntoView({behavior:'smooth',block:'center'});};
window.cancelCampaignEdit=()=>{editingCampaignId=null;render();};

let editingAdvertisementId=null;
window.saveAdvertisement=async()=>{const title=$('#adTitle').value.trim(),description=$('#adDesc').value.trim(),contact_text=$('#adContact').value.trim(),action_url=$('#adUrl').value.trim(),end=$('#adEnd').value,featured=$('#adFeatured').checked,file=$('#adImage')?.files?.[0];if(!title||!end)return toast('Complete el título y la fecha de finalización.');if(new Date(end+'T23:59:59').getTime()<Date.now())return toast('La fecha final debe ser hoy o una fecha futura.');try{const image=await promoImageDataUrl(file);if(!image&&!editingAdvertisementId)return toast('Seleccione una imagen publicitaria.');const payload={title,description,contact_text:contact_text||null,action_url:action_url||null,starts_at:new Date().toISOString(),ends_at:new Date(end+'T23:59:59').toISOString(),active:true,featured,updated_at:new Date().toISOString()};if(image)payload.image_url=image;const q=editingAdvertisementId?sb.from('advertisements').update(payload).eq('id',editingAdvertisementId):sb.from('advertisements').insert(payload);const {error}=await q;if(error)throw error;editingAdvertisementId=null;toast('Publicidad publicada inmediatamente. Se retirará automáticamente al vencer.');render();}catch(error){toast(error.message||'No se pudo guardar la publicidad.');}};
window.editAdvertisement=async id=>{const {data,error}=await sb.from('advertisements').select('*').eq('id',id).single();if(error)return toast(error.message);editingAdvertisementId=id;$('#adTitle').value=data.title||'';$('#adDesc').value=data.description||'';$('#adContact').value=data.contact_text||'';$('#adUrl').value=data.action_url||'';$('#adEnd').value=(data.ends_at||'').slice(0,10);$('#adFeatured').checked=!!data.featured;$('#saveAdBtn').textContent='Guardar cambios';$('#cancelAdBtn').classList.remove('hidden');$('#adTitle').scrollIntoView({behavior:'smooth',block:'center'});};
window.cancelAdvertisementEdit=()=>{editingAdvertisementId=null;render();};

window.togglePromo=async(type,id,field,value)=>{const table=type==='coupon'?'coupons':type==='advertisement'?'advertisements':'campaigns';const {error}=await sb.from(table).update({[field]:value,updated_at:new Date().toISOString()}).eq('id',id);if(error)return toast(error.message);render();};
window.deletePromo=async(type,id)=>{if(!confirm(`¿Eliminar definitivamente este ${type==='coupon'?'cupón':type==='advertisement'?'publicidad':'campaña'}?`))return;const table=type==='coupon'?'coupons':type==='advertisement'?'advertisements':'campaigns';const {error}=await sb.from(table).delete().eq('id',id);if(error)return toast(error.message.includes('foreign key')?'Este cupón tiene solicitudes. Ocúltelo para conservar el historial.':error.message);toast('Eliminado.');render();};
window.requestVirtualContact=async()=>{
  const message=$('#vcMessage')?.value.trim();if(!message)return toast('Escriba brevemente el motivo de la solicitud.');
  const payload={patient_id:session.user.id,family_member_id:activeFamilyMemberId,request_type:$('#vcType').value,message,status:'requested',preferred_at:$('#vcPreferred').value?new Date($('#vcPreferred').value).toISOString():null};
  const {error}=await sb.from('virtual_contacts').insert(payload);if(error)return toast(error.message);toast('Solicitud virtual enviada correctamente.');render();
};
window.requestCoupon=async couponId=>{const {data:existing,error:checkError}=await sb.from('coupon_redemptions').select('id,status').eq('coupon_id',couponId).eq('patient_id',session.user.id).in('status',['requested','approved','used']).limit(1);if(checkError)return toast(checkError.message);if(existing?.length)return toast(existing[0].status==='approved'?'Este cupón ya fue aprobado.':existing[0].status==='used'?'Este cupón ya fue utilizado.':'Este cupón ya fue solicitado.');if(!confirm('¿Desea solicitar el uso de este cupón?'))return;const {error}=await sb.from('coupon_redemptions').insert({coupon_id:couponId,patient_id:session.user.id,status:'requested'});if(error)return toast(error.code==='23505'?'Este cupón ya fue solicitado.':error.message);document.getElementById('promotionDetailModal')?.remove();toast('Cupón solicitado. Su médico debe validarlo.');render();};
window.setVirtualContact=async(id,status)=>{let response_text='';if(status==='approved')response_text=prompt('Mensaje para el paciente (opcional):','Solicitud aprobada. Coordinaremos el contacto.')||'';if(status==='rejected')response_text=prompt('Motivo del rechazo (opcional):','Se recomienda agendar una consulta formal.')||'';const payload={status,response_text,updated_at:new Date().toISOString()};if(status==='used')payload.used_at=new Date().toISOString();if(status==='approved')payload.approved_at=new Date().toISOString();const {error}=await sb.from('virtual_contacts').update(payload).eq('id',id);if(error)return toast(error.message);toast(status==='used'?'Contacto marcado como atendido.':'Solicitud actualizada.');render();};
window.setCouponRedemption=async(id,status)=>{const payload={status,updated_at:new Date().toISOString()};if(status==='approved')payload.approved_at=new Date().toISOString();if(status==='used')payload.used_at=new Date().toISOString();const {error}=await sb.from('coupon_redemptions').update(payload).eq('id',id);if(error)return toast(error.message);toast(status==='used'?'Cupón marcado como utilizado.':'Cupón actualizado.');render();};

window.sendPasswordReset=async email=>{
  if(!email)return toast('La cuenta no tiene correo registrado.');
  if(!confirm(`¿Enviar un enlace de recuperación a ${email}?`))return;
  const {error}=await sb.auth.resetPasswordForEmail(email,{redirectTo:`${location.origin}${location.pathname}`});
  if(error)return toast(error.message);
  toast('Enlace de recuperación enviado.');
};
window.openAccountProfile=async id=>{activeAdminFamilyMemberId=null;await openPatient(id);};

document.addEventListener('click', async event=>{
  const promoBtn=event.target.closest('[data-promo-type][data-promo-id]');if(promoBtn){openPromotionDetail(promoBtn.dataset.promoType,promoBtn.dataset.promoId);return;}
  const scrollBtn=event.target.closest('[data-scroll-target]');
  if(scrollBtn){const target=document.getElementById(scrollBtn.dataset.scrollTarget);if(target)target.scrollIntoView({behavior:'smooth',block:'start'});return;}
  const btn=event.target.closest('[data-go],[data-action]');
  if(!btn)return;
  if(btn.dataset.go){document.getElementById('promotionDetailModal')?.remove();await navigateTo(btn.dataset.go);return;}
  const action=btn.dataset.action;
  try{
    if(action==='mark-read') return await window.markRead(btn.dataset.id);
    if(action==='archive-cancellation-alert') return await window.archiveCancellationAlert(btn.dataset.id);
    if(action==='resolve-health-alert') return await window.resolveHealthAlert(btn.dataset.id);
    if(action==='resolve-all-informational-alerts') return await window.resolveAllInformationalAlerts();
    if(action==='archive-closed-appointments') return await window.archiveClosedAppointments();
    if(action==='open-doc') return await window.openDoc(btn.dataset.path);
    if(action==='download-doc') return await window.downloadDoc(btn.dataset.path,btn.dataset.title||'Documento médico');
    if(action==='share-doc') return await window.shareDoc(btn.dataset.path,btn.dataset.title||'Documento médico');
    if(action==='review-record') return await window.reviewRecord(btn.dataset.id);
    if(action==='edit-patient-control') return await window.editPatientControl(btn.dataset.id);
    if(action==='hide-patient-control') return await window.hidePatientControl(btn.dataset.id);
    if(action==='hide-admin-control') return await window.hideAdminControl(btn.dataset.id);
    if(action==='review-doc') return await window.reviewDoc(btn.dataset.id);
    if(action==='set-appt') return await window.setAppt(btn.dataset.id,btn.dataset.status,btn.dataset.patient);
    if(action==='cancel-patient-appointment') return await window.cancelPatientAppointment(btn.dataset.id);
    if(action==='agenda-week-prev') return await window.changeAgendaWeek(-1);
    if(action==='agenda-week-next') return await window.changeAgendaWeek(1);
    if(action==='agenda-week-today') return await window.resetAgendaWeek();
    if(action==='save-coupon') return await window.saveCoupon();
    if(action==='cancel-coupon-edit') return window.cancelCouponEdit();
    if(action==='save-campaign') return await window.saveCampaign();
    if(action==='cancel-campaign-edit') return window.cancelCampaignEdit();
    if(action==='save-advertisement') return await window.saveAdvertisement();
    if(action==='cancel-advertisement-edit') return window.cancelAdvertisementEdit();
    if(action==='edit-coupon') return await window.editCoupon(btn.dataset.id);
    if(action==='edit-campaign') return await window.editCampaign(btn.dataset.id);
    if(action==='edit-advertisement') return await window.editAdvertisement(btn.dataset.id);
    if(action==='toggle-coupon') return await window.togglePromo('coupon',btn.dataset.id,'active',btn.dataset.active!=='true');
    if(action==='toggle-campaign') return await window.togglePromo('campaign',btn.dataset.id,'active',btn.dataset.active!=='true');
    if(action==='toggle-advertisement') return await window.togglePromo('advertisement',btn.dataset.id,'active',btn.dataset.active!=='true');
    if(action==='feature-coupon') return await window.togglePromo('coupon',btn.dataset.id,'featured',btn.dataset.featured!=='true');
    if(action==='feature-campaign') return await window.togglePromo('campaign',btn.dataset.id,'featured',btn.dataset.featured!=='true');
    if(action==='feature-advertisement') return await window.togglePromo('advertisement',btn.dataset.id,'featured',btn.dataset.featured!=='true');
    if(action==='delete-coupon') return await window.deletePromo('coupon',btn.dataset.id);
    if(action==='delete-campaign') return await window.deletePromo('campaign',btn.dataset.id);
    if(action==='delete-advertisement') return await window.deletePromo('advertisement',btn.dataset.id);
    if(action==='campaign-cta'){document.getElementById('promotionDetailModal')?.remove();if(btn.dataset.destination==='external'){if(btn.dataset.url)window.open(btn.dataset.url,'_blank','noopener,noreferrer');else toast('Esta campaña no tiene un enlace configurado.');}else if(btn.dataset.destination==='coupons'){await navigateTo('benefits');setTimeout(()=>document.getElementById('couponSection')?.scrollIntoView({behavior:'smooth',block:'start'}),120);}else{await navigateTo('appointments');}return;}
    if(action==='open-ad-link'){const url=btn.dataset.url;document.getElementById('promotionDetailModal')?.remove();if(url)window.open(url,'_blank','noopener,noreferrer');return;}
    if(action==='request-virtual-contact') return await window.requestVirtualContact();
    if(action==='request-coupon') return await window.requestCoupon(btn.dataset.id);
    if(action==='set-virtual') return await window.setVirtualContact(btn.dataset.id,btn.dataset.status);
    if(action==='set-coupon') return await window.setCouponRedemption(btn.dataset.id,btn.dataset.status);
    if(action==='scroll-management-benefits'){document.getElementById('benefitFormTitle')?.scrollIntoView({behavior:'smooth',block:'start'});return;}
    if(action==='save-settings') return await window.saveSettings();
    if(action==='create-benefit') return await window.createBenefit();
    if(action==='edit-benefit') return await window.editBenefit(btn.dataset.id);
    if(action==='update-benefit') return await window.updateBenefit();
    if(action==='cancel-benefit-edit') return window.cancelBenefitEdit();
    if(action==='toggle-benefit') return await window.toggleBenefit(btn.dataset.id,btn.dataset.active==='true');
    if(action==='delete-benefit') return await window.deleteBenefit(btn.dataset.id);
    if(action==='open-inactive-patient') return window.openInactivePatient(btn.dataset.id);
    if(action==='invite-inactive-patient') return await window.inviteInactivePatient(btn.dataset.id);
    if(action==='delete-inactive-patient') return await window.deleteInactivePatient(btn.dataset.id,btn.dataset.name||'este paciente');
    if(action==='open-inactive-doc') return await window.openInactiveDoc(btn.dataset.path);
    if(action==='share-inactive-doc') return await window.shareInactiveDoc(btn.dataset.id,btn.dataset.path,btn.dataset.title||'Documento');
    if(action==='delete-inactive-doc') return await window.deleteInactiveDoc(btn.dataset.id,btn.dataset.path);
    if(action==='delete-patient') return await window.deletePatient(btn.dataset.id,btn.dataset.name||'este paciente');
    if(action==='send-password-reset') return await window.sendPasswordReset(btn.dataset.email);
    if(action==='open-account') return await window.openAccountProfile(btn.dataset.id);
    if(action==='create-availability-rule') return await window.createAvailabilityRule();
    if(action==='delete-availability-rule') return await window.deleteAvailabilityRule(btn.dataset.id);
    if(action==='create-availability-block') return await window.createAvailabilityBlock();
    if(action==='delete-availability-block') return await window.deleteAvailabilityBlock(btn.dataset.id);
  }catch(err){ console.error(err); toast(err.message||'No se pudo completar la acción.'); }
});
updateLiveDateTime();
if('serviceWorker' in navigator){
  navigator.serviceWorker.register('./sw.js',{updateViaCache:'none'}).catch(err=>console.warn('PWA SW:',err));
}
initOneSignal().catch(err=>{console.warn('OneSignal pendiente:',err);refreshNotificationButton('error');});

window.addEventListener('pagehide',saveAdminContext);
document.addEventListener('visibilitychange',()=>{if(document.visibilityState==='hidden')saveAdminContext();});
boot();

/* Deslizar hacia abajo para actualizar, seguro para panel administrativo y pacientes */
(function enablePullToRefresh(){
  const threshold=78;
  let startY=0;
  let distance=0;
  let tracking=false;
  let refreshing=false;
  const indicator=document.createElement('div');
  indicator.className='pull-refresh-indicator';
  indicator.innerHTML='<span class="pull-refresh-icon">↻</span><span class="pull-refresh-text">Deslice para actualizar</span>';
  document.body.appendChild(indicator);
  const text=()=>indicator.querySelector('.pull-refresh-text');

  document.addEventListener('touchstart',event=>{
    if(refreshing||window.scrollY>2||document.querySelector('#appView.hidden'))return;
    if(event.touches.length!==1)return;
    startY=event.touches[0].clientY;
    distance=0;
    tracking=true;
  },{passive:true});

  document.addEventListener('touchmove',event=>{
    if(!tracking||refreshing||event.touches.length!==1)return;
    distance=Math.max(0,event.touches[0].clientY-startY);
    if(distance<12)return;
    indicator.classList.add('visible');
    text().textContent=distance>=threshold?'Suelte para actualizar':'Deslice para actualizar';
  },{passive:true});

  document.addEventListener('touchend',async()=>{
    if(!tracking)return;
    tracking=false;
    if(distance<threshold){
      indicator.classList.remove('visible');
      distance=0;
      return;
    }
    refreshing=true;
    indicator.classList.add('visible','refreshing');
    text().textContent='Actualizando…';
    try{
      await render();
      text().textContent='Actualizado';
      toast('Información actualizada.');
    }catch(err){
      console.error('Actualización por gesto:',err);
      text().textContent='No se pudo actualizar';
    }
    setTimeout(()=>{
      indicator.classList.remove('visible','refreshing');
      refreshing=false;
      distance=0;
    },900);
  },{passive:true});

  document.addEventListener('touchcancel',()=>{
    tracking=false;
    distance=0;
    indicator.classList.remove('visible');
  },{passive:true});
})();
