${c.modules.length} módulos · ${countLessons(c)} aulas
${c.modules.reduce((n,m)=>n+m.lessons.filter(l=>l.status==='published').length,0)} publicadas nesta prévia
const B=window.psiqManageBridge; const $=id=>document.getElementById(id); const esc=value=>String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const uid=()=>crypto.randomUUID(); const roles={dev:'Dev',admin:'Admin',editor:'Editor de cursos',financeiro:'Financeiro'}; const canManage=()=>['dev','admin','editor'].includes(B.role()); const urls=new Set();let editing=null,search='',fiscal='';const opened=new Set(); const seed=await fetch('/management-seed.json').then(r=>{if(!r.ok)throw new Error('catalog');return r.json()}).catch(()=>[]); const courses=Object.entries(B.library()).filter(([,c])=>!c.isRecording).map(([id,c])=>({id,name:c.name,modules:c.modules.map(m=>({id:m.id,name:m.name,lessons:m.lessons.map(l=>({id:l.id,title:l.title,description:l.description||'',status:'draft',assets:{pdf:null,mapa:null,quiz:null,cover:null,video:null,outros:[]},studentId:l.id}))}))})); const base=courses.find(c=>c.id==='base');if(seed.length&&base)base.modules=[{id:'base-principal',name:'Psiq Base: base teórica',lessons:seed.map(a=>({id:a.id,title:`Aula ${String(a.numero).padStart(2,'0')} · ${a.titulo}`,description:'Materiais importados do acervo local. Publicação ainda não definida.',status:'draft',studentId:'base-aula-'+String(a.numero).padStart(2,'0'),assets:{pdf:a.materiais.pdf?{name:a.materiais.pdf.split('/').at(-1),url:`/materiais/${a.id}/pdf`,review:'pending'}:null,mapa:a.materiais.mapa?{name:a.materiais.mapa.split('/').at(-1),url:`/materiais/${a.id}/mapa`,review:'pending'}:null,quiz:a.materiais.quiz?{name:a.materiais.quiz.split('/').at(-1),url:`/materiais/${a.id}/quiz`,review:'pending'}:null,cover:null,video:a.numero===1?{name:'Vídeo local da aula 01',url:'/video-teste'}:null,outros:[]}}))}]; const course=()=>courses.find(c=>c.id===B.course())||courses[0]; const findLesson=id=>{for(const c of courses)for(const m of c.modules){const l=m.lessons.find(l=>l.id===id);if(l)return {c,m,l}}return null}; const pill=(text,kind='')=>`${esc(text)}`; const countLessons=c=>c.modules.reduce((n,m)=>n+m.lessons.length,0); const allLessons=()=>courses.flatMap(c=>c.modules.flatMap(m=>m.lessons)); function repaint(){B.render()} function go(key){if(location.hash==='#adm-'+key)repaint();else location.hash='adm-'+key} function notice(text){window.notify(text)} function ready(l){return Boolean(l.title.trim()&&l.assets.video&&l.assets.cover&&['pdf','mapa','quiz'].every(k=>l.assets[k]?.review==='approved')&&l.assets.outros.every(a=>a.review==='approved'))} Object.assign(B.titles,{hoje:['Painel','Visão geral de vendas, conteúdo e alunos. Dados de demonstração.'],cursos:['Gestão de cursos','Abra um curso para organizar módulos, aulas e materiais.'],editor:['Gestão de cursos','Módulos, aulas e publicação do conteúdo.'],financeiro:['Financeiro e vendas','Recebimentos e cobranças de demonstração, com filtros e extrato.'],equipe:['Equipe e permissões','Quatro acessos: Dev, Admin, Editor de cursos e Financeiro.'],config:['Configurações','Conciliação e fiscal, aparência e configurações técnicas.'],comunidade:['Comunidade','Espaços, publicações e acompanhamento da comunidade.']}); function dashboard(){const sales=B.sales(),sum=status=>sales.filter(s=>s.status===status).reduce((n,s)=>n+s.amount,0),lessons=allLessons();return B.kpis([['RECEBIDO',B.money(sum('Recebido')),'Resumo dos filtros do Financeiro'],['EM ABERTO',B.money(sum('Em aberto')),'Não inclui valores recebidos'],['AULAS NÃO PUBLICADAS',String(lessons.filter(l=>l.status!=='published').length),'Conteúdo em preparação'],['ALUNOS ATIVOS','24','Número fictício de demonstração']])+`
Publicações e alterações ficam somente nesta prévia.
Conciliação e fiscal ficam aqui. APIs e permissões têm seções próprias, acessíveis somente ao Dev.
| Acesso | Escopo | Restrição |
|---|---|---|
| Dev | Toda a plataforma | Inclui APIs, permissões e configurações |
| Admin | Operação, conteúdo, alunos, financeiro e comunidade | Sem APIs, permissões e configurações técnicas |
| Editor de cursos | Módulos, aulas, capas, vídeos e materiais | Sem financeiro ou configurações |
| Financeiro | Financeiro e vendas | Sem gestão de cursos ou configurações |
Não há um acesso separado de moderador. Admin é o acesso administrativo do proprietário.
Esta matriz modela o front. O backend ainda deverá conferir cada operação e registrar alterações.
${c.modules.length} módulos · ${countLessons(c)} aulas
${c.modules.reduce((n,m)=>n+m.lessons.filter(l=>l.status==='published').length,0)} publicadas nesta prévia
Crie módulos, abra as aulas existentes e edite vídeo, capa, PDF, mapa mental, quiz e outros anexos separadamente.
${c.modules.length} módulos · ${countLessons(c)} aulas. Os módulos começam recolhidos.
Nenhuma aula neste módulo.
'}Ainda não adicionado.
'}Vídeo ainda não adicionado.
'}${esc(l.description)}
Prévia de conteúdo; não altera a publicação para alunos.
`;modal.showModal()} function saveLesson(publish){capture();const l=editing.lesson;if(publish)l.status='published';if(l.status==='published'&&!ready(l)){notice('Para publicar, adicione vídeo e capa e aprove PDF, mapa mental, quiz e outros anexos.');return}const c=course(),target=c.modules.find(m=>m.id===editing.moduleId);if(!target){notice('Escolha um módulo válido.');return}const old=findLesson(l.id);if(old&&old.m.id===target.id)old.m.lessons[old.m.lessons.indexOf(old.l)]=structuredClone(l);else{if(old)old.m.lessons.splice(old.m.lessons.indexOf(old.l),1);target.lessons.push(structuredClone(l))}opened.add(target.id);editing=null;repaint();notice(l.status==='published'?'Aula publicada somente nesta prévia.':'Alterações salvas nesta sessão. Aula não publicada.')} function localAsset(file,key){const url=URL.createObjectURL(file);urls.add(url);let preview=null;if(['mapa','quiz'].includes(key)){const wrapper=`Excluir ${esc(m.name)} e suas ${m.lessons.length} aulas desta prévia? Os arquivos originais serão preservados.
`,()=>{c.modules.splice(c.modules.indexOf(m),1);repaint()},'Excluir');break} case 'manageOrderModule':move(c.modules,c.modules.findIndex(m=>m.id===id),Number(b.dataset.direction));break; case 'manageNewLesson':beginEdit(null,id);break;case 'manageEdit':beginEdit(id);break; case 'manageDuplicate':{const copy=structuredClone(found.l);copy.id=uid();copy.title+=' (cópia)';copy.status='draft';found.m.lessons.splice(found.m.lessons.indexOf(found.l)+1,0,copy);opened.add(found.m.id);repaint();break} case 'managePreview':previewLesson(id);break; case 'manageMove':dialog('Mover aula',``,()=>{const target=c.modules.find(m=>m.id===$('manage-dialog-module').value);found.m.lessons.splice(found.m.lessons.indexOf(found.l),1);target.lessons.push(found.l);opened.add(target.id);repaint()},'Mover');break; case 'manageOrderLesson':move(found.m.lessons,found.m.lessons.indexOf(found.l),Number(b.dataset.direction));break; case 'manageStatus':if(found.l.status==='published'){found.l.status='draft';repaint()}else if(ready(found.l)){found.l.status='published';repaint();notice('Publicação simulada nesta sessão.')}else{beginEdit(id);notice('Adicione os arquivos e aprove os materiais antes de publicar.')}break; case 'manageDelete':dialog('Excluir aula',`Excluir ${esc(found.l.title)} desta prévia?
`,()=>{found.m.lessons.splice(found.m.lessons.indexOf(found.l),1);repaint()},'Excluir');break; case 'manageBack':case 'manageCancel':editing=null;repaint();break; case 'manageApprove':capture();{const a=editing.lesson.assets[id];a.review=a.review==='approved'?'pending':'approved';if(a.review==='pending')editing.lesson.status='draft';repaint()}break; case 'manageRemoveAsset':capture();editing.lesson.assets[id]=null;editing.lesson.status='draft';repaint();break; case 'manageApproveOther':capture();{const a=editing.lesson.assets.outros[Number(id)];a.review=a.review==='approved'?'pending':'approved';editing.lesson.status='draft';repaint()}break; case 'manageRemoveOther':capture();editing.lesson.assets.outros.splice(Number(id),1);editing.lesson.status='draft';repaint();break; }},true); document.addEventListener('submit',e=>{if(e.target.id==='manage-dialog-form'){e.preventDefault();if(canManage()&&modalAction?.()!==false)modal.close()}if(e.target.id==='manage-lesson-form'){e.preventDefault();if(canManage())saveLesson(e.submitter?.hasAttribute('data-manage-publish'))}}); document.addEventListener('change',e=>{const key=e.target.dataset.manageFile;if(!key||!editing||!canManage())return;capture();const files=[...e.target.files];if(files.some(f=>!valid(f,key))){notice('Arquivo incompatível. Confira o formato indicado; capas devem ter até 10 MB.');e.target.value='';return}if(key==='outros')editing.lesson.assets.outros.push(...files.map(f=>localAsset(f,key)));else if(files[0])editing.lesson.assets[key]=localAsset(files[0],key);editing.lesson.status='draft';repaint()}); document.addEventListener('input',e=>{if(e.target.id==='manage-search'){search=e.target.value;const p=e.target.selectionStart;repaint();$('manage-search').focus();$('manage-search').setSelectionRange(p,p)}}); document.addEventListener('toggle',e=>{if(e.target.matches('.manage-module')){const id=e.target.dataset.manageModule;e.target.open?opened.add(id):opened.delete(id)}},true); document.addEventListener('click',e=>{document.querySelectorAll('.manage-actions[open]').forEach(d=>{if(!d.contains(e.target))d.open=false});const mode=e.target.closest('[data-access-mode]')?.dataset.accessMode;if(mode)queueMicrotask(()=>{const p=$('login-role-copy');if(p&&roles[mode])p.textContent={dev:'Acesso completo, incluindo APIs, permissões e configurações técnicas.',admin:'Gestão da plataforma, sem APIs, permissões ou configuração técnica.',editor:'Acesso somente à gestão de cursos, módulos, aulas e materiais.',financeiro:'Acesso somente ao painel financeiro, vendas e cobranças.'}[mode]})}); window.addEventListener('beforeunload',()=>urls.forEach(url=>URL.revokeObjectURL(url))); window.psiqManagementReady=true;if(!$('admin-shell').hidden)repaint(); // Integrações: catálogo visual autorizado em 29/10/2026. const externalTools=[ {id:'panda',name:'Panda Video',mark:'P',category:'Vídeos',description:'Hospedagem e reprodução das aulas.',features:['Vídeos vinculados às aulas','Capa independente do vídeo','Acompanhamento do processamento'],purpose:'Reproduzir as aulas na área de membros.'}, {id:'bling',name:'Bling',mark:'B',category:'Financeiro',description:'Organização fiscal e integração com a gestão financeira.',features:['Dados fiscais e notas','Vínculo com vendas e clientes','Acompanhamento da sincronização'],purpose:'Integrar os dados fiscais às vendas da plataforma.'}, {id:'claude',name:'Claude · Anthropic',mark:'C',category:'Inteligência artificial',description:'Produção de materiais a partir do conteúdo das aulas.',features:['Relatório da aula','Mapa mental em HTML','Quiz interativo com revisão'],purpose:'Gerar relatório, mapa mental e quiz para aprovação.'}, {id:'openai',name:'ChatGPT · OpenAI',mark:'O',category:'Inteligência artificial',description:'Recursos de IA para os fluxos de conteúdo da plataforma.',features:['Apoio à produção de materiais','Transcrição a configurar','Geração de capas a configurar'],purpose:'Apoiar os fluxos de IA e a produção de conteúdo.'} ]; const connectionDrafts=new Map();let integrationTab='todas',integrationCategory='Todas'; B.titles.integracoes=['Integrações','Gerencie as ferramentas externas da plataforma.']; function integrationsView(){if(B.role()!=='dev')return 'Quatro ferramentas previstas. Nenhuma conexão real foi realizada.
${t.description}
${connectionDrafts.has(t.id)?'Planejamento salvo nesta sessão. Conexão pendente.':'Aguardando configuração segura no backend.'}
Quando as conexões reais forem implementadas e validadas, elas aparecerão aqui.
As funções acima são o planejamento da integração. Credenciais, conexão, testes e sincronização dependem do backend. Esta tela não solicita nem armazena chaves de API.
`} window.adminIntegrations=integrationsView; document.addEventListener('click',e=>{const tab=e.target.closest('[data-connection-tab]'),button=e.target.closest('[data-connection-manage]');if(!tab&&!button)return;if(B.role()!=='dev')return;e.preventDefault();e.stopImmediatePropagation();if(tab){integrationTab=tab.dataset.connectionTab;if(integrationTab!=='categorias')integrationCategory='Todas';repaint();return}const t=externalTools.find(t=>t.id===button.dataset.connectionManage);const draft=connectionDrafts.get(t.id);dialog(`Configurar ${t.name}`,`${t.description}
Não cole senhas, tokens ou chaves aqui. A conexão segura será feita no backend, com acesso exclusivo do Dev.
Salvar registra somente o planejamento nesta sessão. Não conecta a ferramenta.
`,()=>{connectionDrafts.set(t.id,{purpose:$('connection-purpose').value,account:$('connection-account').value});repaint();notice('Planejamento salvo nesta sessão. Ferramenta ainda não conectada.')},'Salvar planejamento')},true); document.addEventListener('change',e=>{if(e.target.id==='connection-category'&&B.role()==='dev'){integrationCategory=e.target.value;repaint()}}); if(!$('admin-shell').hidden)repaint();