1window.initBrandCenterMail=function({client,mount,profile={},admin=false}){ 2 const pt=value=>window.proposalI18n?window.proposalI18n.text(value):value; 3 const phtml=(parts,...values)=>parts.map((part,i)=>pt(part)+(i<values.length?values[i]:'')).join(''); 4 const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); 5 const button=document.createElement('button');button.type='button';button.className='bc-mail-trigger';button.textContent=admin?pt('Historial de correos'):pt('Mi cuenta');mount.append(button); 6 const dialog=document.createElement('dialog');dialog.className='bc-mail-dialog';dialog.setAttribute('aria-label',admin?pt('Historial de correos'):pt('Mi cuenta y correos'));document.body.append(dialog);let destroyed=false; 7 const labels={pending:'En cola',processing:'Enviando',sent:'Enviado · entrega pendiente',review:'Requiere revisión',cancelled:'Cancelado',delivered:'Entregado',failed:'No entregado',deferred:'Entrega demorada'}; 8 async function api(body){const {data:{session}}=await client.auth.getSession();if(!session)throw Error(pt('Vuelve a iniciar sesión.'));const response=await fetch('https://zqpubajyhpuyjahrjhnv.supabase.co/functions/v1/brand-center-mail',{method:'POST',headers:{Authorization:'Bearer '+session.access_token,'Content-Type':'application/json'},body:JSON.stringify(body)});const data=await response.json();if(!response.ok||!data.ok)throw Error(({destinatario_distinto:pt('Este enlace ya se envió a otro correo. Genera un enlace nuevo para cambiar el destinatario.'),enlace_vencido:pt('El enlace ya venció. Genera uno nuevo.'),demasiados_intentos:pt('Espera unos minutos antes de volver a enviar.')})[data.error]||pt('No pudimos completar la operación. Intenta de nuevo.'));return data;} 9 function shell(title,body){dialog.innerHTML=phtml`<div class="bc-mail-heading"><h2>${esc(title)}</h2><button type="button" data-close aria-label="Cerrar">Ã</button></div>${body}<p data-mail-status role="status"></p>`;dialog.querySelector('[data-close]').onclick=()=>dialog.close();if(!dialog.open)dialog.showModal();} 10 function message(t){if(!destroyed)dialog.querySelector('[data-mail-status]').textContent=t;} 11 const date=v=>v?new Date(v).toLocaleString(window.proposalI18n?.locale||'es-MX',{dateStyle:'medium',timeStyle:'short'}):'â'; 12 async function history(offset=0){ 13 shell(admin?pt('Historial de correos'):pt('Correos de mis propuestas'),pt('<p>Cargando envÃosâ¦</p>')); 14 try{const data=await api({action:'history',admin,offset});if(destroyed)return; 15 shell(admin?pt('Historial de correos'):pt('Correos de mis propuestas'),phtml`<p>«Enviado» significa que Brevo recibió el mensaje. «Entregado» confirma la recepción en el servidor de correo del destinatario. La actualización puede tardar unos minutos.</p><div class="bc-mail-tools"><button data-refresh>Actualizar</button>${admin?'':pt('<button data-account>Mi cuenta</button>')}</div><div class="bc-mail-log">${data.emails.length?data.emails.map(m=>{const state=['delivered','failed','deferred'].includes(m.delivery_status)?m.delivery_status:m.status;return phtml`<article><span class="bc-mail-state ${state}">${pt(labels[state]||state)}</span><h3>${esc(m.subject)}</h3><p>${esc(m.recipient||pt('Destinatario pendiente'))}</p><small>${date(m.created_at)} · ${m.attempts} intento(s)</small>${m.delivered_at?phtml`<p>Entregado: ${date(m.delivered_at)}</p>`:''}${state==='review'||state==='failed'?pt('<p class="bc-mail-warning">Revisa la dirección y el estado del servicio antes de volver a enviar. Para accesos, usa «Reenviar invitación» o «Olvidé mi contraseña» y genera un enlace vigente.</p>'):''}${m.document_id?phtml`<a href="/propuestas/?document=${encodeURIComponent(m.document_id)}">Abrir propuesta â</a>`:''}</article>`}).join(''):pt('<p>Aún no hay envÃos para mostrar.</p>')}</div><div class="bc-mail-tools">${offset?pt('<button data-previous>Anterior</button>'):''}${data.has_more?pt('<button data-next>Siguiente</button>'):''}</div>`); 16 dialog.querySelector('[data-refresh]').onclick=()=>history(offset);dialog.querySelector('[data-account]')?.addEventListener('click',account);dialog.querySelector('[data-previous]')?.addEventListener('click',()=>history(Math.max(0,offset-50)));dialog.querySelector('[data-next]')?.addEventListener('click',()=>history(offset+50)); 17 }catch(e){message(e.message);} 18 } 19 async function account(){ 20 shell(pt('Mi cuenta'),pt('<p>Cargando preferenciasâ¦</p>')); 21 try{const [{data:{user}},prefs]=await Promise.all([client.auth.getUser(),api({action:'preferences'})]);if(destroyed)return; 22 shell(pt('Mi cuenta'),phtml`<p>${esc(profile.nombre||'')}<br><strong>${esc(user.email)}</strong></p><form data-email-change><label>Nuevo correo de acceso<input name="email" type="email" autocomplete="email" required maxlength="254" ${user.email==='[email protected]'?'disabled':''}></label><button type="submit" ${user.email==='[email protected]'?'disabled':''}>Solicitar cambio de correo</button><small>${user.email==='[email protected]'?pt('La dirección de la cuenta raÃz se conserva para administrar el portal.'):pt('Recibirás los enlaces necesarios para confirmar el cambio. Tu acceso se conserva hasta completarlo.')}</small></form><button data-reset>Restablecer mi contraseña</button><form data-mail-prefs><h3>Mis avisos</h3><label class="bc-mail-check"><input name="materials" type="checkbox" ${prefs.materials?'checked':''}>Nuevos materiales disponibles para mi cuenta</label><label class="bc-mail-check"><input name="reminders" type="checkbox" ${prefs.reminders?'checked':''}>Recordatorios de vencimiento y seguimiento comercial</label><small>Los avisos de seguridad y las confirmaciones de interés se mantienen activos.</small><button type="submit">Guardar preferencias</button></form><button data-history>Ver correos de mis propuestas</button>`); 23 dialog.querySelector('[data-history]').onclick=()=>history(); 24 dialog.querySelector('[data-mail-prefs]').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,b=f.querySelector('button');b.disabled=true;try{await api({action:'preferences',materials:f.elements.materials.checked,reminders:f.elements.reminders.checked});message(pt('Preferencias guardadas.'));}catch(err){message(err.message);}finally{b.disabled=false;}}; 25 dialog.querySelector('[data-reset]').onclick=async e=>{e.currentTarget.disabled=true;const {error}=await client.auth.resetPasswordForEmail(user.email,{redirectTo:'https://amitai-brand-center.netlify.app/'});message(error?pt('No pudimos enviar el enlace. Intenta en unos minutos.'):pt('Te enviamos un enlace para elegir una nueva contraseña.'));e.target.disabled=false;}; 26 dialog.querySelector('[data-email-change]').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,b=f.querySelector('button');b.disabled=true;try{const {error}=await client.auth.updateUser({email:f.elements.email.value.trim()},{emailRedirectTo:'https://amitai-brand-center.netlify.app/'});if(error)throw error;message(pt('Revisa tu correo actual y el nuevo para confirmar el cambio.'));}catch{message(pt('No pudimos solicitar el cambio. Revisa la dirección o intenta en unos minutos.'));}finally{b.disabled=false;}}; 27 }catch(e){message(e.message);} 28 } 29 async function sendProposal(link,doc){ 30 const requestId=crypto.randomUUID(),email=link.recipient_email||doc.clientEmail||'';
31 shell(pt('Enviar propuesta al cliente'),phtml`<p><strong>${esc(doc.folio)}</strong> · Versión ${Number(link.version)}</p><p>El cliente recibirá el enlace de esta versión. Comparte la contraseña por separado.</p><form data-send-proposal><label>Correo del cliente<input name="email" type="email" required maxlength="254" value="${esc(email)}" ${link.recipient_email?'readonly':''}></label><button type="submit">Enviar por correo</button></form>`); 32 dialog.querySelector('form').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,b=f.querySelector('button');b.disabled=true;try{await api({action:'send_proposal',linkId:link.id,email:f.elements.email.value.trim(),requestId});link.recipient_email=f.elements.email.value.trim().toLowerCase();message(pt('Correo en cola. Puedes consultar su entrega en el historial de correos.'));b.textContent=pt('EnvÃo registrado');}catch(err){message(err.message);b.disabled=false;}}; 33 } 34 button.onclick=()=>admin?history():account(); 35 document.addEventListener('proposal:language',()=>{button.textContent=pt(admin?pt('Historial de correos'):pt('Mi cuenta'))}); 36 return {api,history,account,sendProposal,destroy(){destroyed=true;dialog.close();dialog.remove();button.remove();}}; 37};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.