🔐 BLUE SKY SCHEDULE PRO

Verificando licença...

Empresa
Cliente Teste
Status
Teste
Dias restantes
--
Renovação
--
Código deste computador:
----

Importante: após 7 dias o sistema bloqueia. O backup da versão teste pode ser reaproveitado quando o cliente comprar a versão correta.

BLUE SKY SOLUTIONS — SCHEDULE

Programação Atual
Empresa: Cliente Teste • Modo teste
26/04/2026 até 02/05/2026
Semana salva com sucesso.

Escalas

Filtro com opção Todos os funcionários, bordas destacadas, resumo do funcionário e salvamento real da semana.

Calendário

Abra uma nova escala por período. Data sem escala salva aparece zerada para começar do zero.

--
--
Nova data fica zerada até clicar em repetir
--
Loja 1

Área de alterações

Edite o dia, adicione quantas lojas/horários forem necessários, informe intervalos e salve a semana no final. A grade ordena automaticamente pelo primeiro horário.

0,0h
0,0h • R$ 0,00
Escolha um dia para repetir no dia atual
Filtre uma escala existente e repita para outro funcionário

Controle de Funcionamento da Loja

Configure a abertura e o fechamento de cada loja. O sistema valida automaticamente ao trocar de dia, avançar dia, trocar semana ou salvar semana.

0,0h
Loja pronta para validação.
🟢 Escala correta: não mostra mensagem.
🟡 Tempo disponível: mostra “Ainda existem horários disponíveis. Deseja continuar?”
🔴 Tempo ultrapassado: mostra “A escala ultrapassou o tempo da loja. Deseja continuar?”
OK continua. Cancelar volta para corrigir.

Funcionários / Alterações

Aba para cadastrar novo funcionário, alterar nome, cargo, loja, valor por hora, configurações e salvar tudo no final.

Sugestões incluídas nesta aba: nome, cargo, loja, departamento, valor por hora, limite semanal, status e observações/configurações.

Financeiro

Totais do dia, da semana, do mês e comparativos do funcionário selecionado.

Gráficos de desempenho

Comparativo de horas — dia, semana e mês

Comparativo de valores — dia, semana e mês

Comparativo de horas por dia

Comparativo de valores por dia

Relatório visual consolidado

Use esta área para imprimir um comparativo geral com todos os gráficos.

Relatórios

PDF/Impressão com período, todos os funcionários ou funcionário individual. Relatório do funcionário sem mostrar valores.

Folga aparece em amarelo em todos os relatórios. Relatório do funcionário e Escala fim funcionário mostram a soma das horas semanais sem valores.

Administração / Segurança

Backup automático, exportação, controle de versão e ferramentas para manter o sistema rápido e seguro.

Versão
v3.0 COMERCIAL
Versão comercial completa: teste controlado, ativação PRO definitiva, backup, relatórios e impressão profissional.
Backup automático
Ativo
Usuário atual
Administrador
Modo local. As alterações ficam registradas no histórico interno.

Ferramentas rápidas

Sistema pronto para uso offline, portable e instalável.
`); w.document.close(); } function finalEmployeeDayCardHtml(e,d){ const entries=dayEntries(e.id,d); const hrs=employeeHoursForDay(e.id,d); if(entries.length===0){ return `
Folga
--
${e.store} • ${fmtHours(0)}
`; } const parts=entries.map(x=>{ const interval = x.intervalType ? `
● ${x.intervalType} • ${x.intervalMinutes||0} min • até ${fmtTimeFromMinutes(entryEndWithIntervalMinutes(x))}
` : ''; const obs = x.obs ? `
${x.obs}
` : ''; return `
${x.shift}${x.store}
${x.start} - ${x.end}
${x.store} • ${fmtHours(diffHours(x.start,x.end))}
${interval}${obs}
`; }).join(''); return `${parts}
Total do dia: ${fmtHours(hrs)}
`; } function finalEmployeeScheduleSectionHtml(e, dates){ const total=dates.reduce((sum,d)=>sum+employeeHoursForDay(e.id,d),0); const workedDays=dates.filter(d=>employeeHoursForDay(e.id,d)>0).length; const avg=workedDays ? total/workedDays : 0; let html = `

${e.name}

Cargo: ${e.role}   |   Loja base: ${e.store}   |   Período: ${brDate(dates[0])} até ${brDate(dates[dates.length-1])}
Dias trabalhados: ${workedDays}   |   Total da semana: ${fmtHours(total)}   |   Média por dia: ${fmtHours(avg)}
`; html += `${dates.map(d=>``).join('')}${dates.map(d=>``).join('')}
Funcionário${dayNameFromDate(d)} ${brDate(d).slice(0,5)}
${shortName(e.name)}${e.role} • ${e.store}${finalEmployeeDayCardHtml(e,d)}
`; return html; } function finalEmployeeScheduleStyles(){return ``;} function openFinalEmployeeScheduleReport(start, end, empId, dates){ const employees=reportEmployeesForPeriod(empId,start,end).filter(Boolean); let html=`

BLUE SKY SOLUTIONS — ESCALA FIM FUNCIONÁRIO

Período: ${brDate(start)} até ${brDate(end)} • Relatório em formato de quadrados lado a lado para entregar ao funcionário.
`; employees.forEach((e,idx)=>{ if(idx>0) html += `
`; html += finalEmployeeScheduleSectionHtml(e, dates); }); openPrintableWindow('Escala fim funcionário', html, finalEmployeeScheduleStyles()); } function openReport(type){ const start=document.getElementById('repStart').value||state.startDate; const end=document.getElementById('repEnd').value||addDays(state.startDate,periodLength()-1); const empId=document.getElementById('repEmployee').value || 'all'; const dates=[]; for(let d=start; parseDate(d)<=parseDate(end); d=addDays(d,1)) dates.push(d); if(!hasReportDataForPeriod(start,end,empId)){showNoReportAlert(); return;} let html=''; if(type==='employee_final'){ openFinalEmployeeScheduleReport(start,end,empId,dates); return; } if(type==='employee'){ const employees=reportEmployeesForPeriod(empId,start,end).filter(Boolean); let grandDays=0, grandHours=0; html += `

BLUE SKY SOLUTIONS — Relatório do funcionário

Período: ${brDate(start)} até ${brDate(end)}

`; if(empId==='all') html += allEmployeesDailySummaryHtml(employees, dates); employees.forEach(e=>{ const total=dates.reduce((sum,d)=>sum+employeeHoursForDay(e.id,d),0); const workedDays=dates.filter(d=>employeeHoursForDay(e.id,d)>0).length; const avg=workedDays ? total/workedDays : 0; grandDays += workedDays; grandHours += total; html += `

${e.name}

Cargo: ${e.role} • Departamento: ${e.dept} • Loja: ${e.store} • Dias trabalhados: ${workedDays} • Média/dia: ${fmtHours(avg)} • Total de horas: ${fmtHours(total)}
${dailyHoursSummaryHtml(e,dates)}`; html += reportRowsForEmployee(e,dates,false); html += `
DiaDataTurnoLojaInícioFimHorasTotal do diaIntervalo / Obs.
SOMATÓRIA DE ${e.name}${fmtHours(total)}${fmtHours(total)}Dias trabalhados: ${workedDays} • Média/dia: ${fmtHours(avg)}

`; }); if(empId==='all'){ const grandAvg=grandDays ? grandHours/grandDays : 0; html += `

SOMATÓRIA GERAL DOS FUNCIONÁRIOS

Funcionários
${employees.length}
Dias trabalhados
${grandDays}
Total de horas
${fmtHours(grandHours)}
Média por dia
${fmtHours(grandAvg)}
`; } } else { const employees=reportEmployeesForPeriod(empId,start,end).filter(Boolean); let grandDays=0, grandHours=0, grandValue=0; html += `

BLUE SKY SOLUTIONS — Schedule report

Período: ${brDate(start)} até ${brDate(end)}

`; employees.forEach(e=>{ const days=dates.filter(d=>employeeHoursForDay(e.id,d)>0).length; const hrs=dates.reduce((sum,d)=>sum+employeeHoursForDay(e.id,d),0); const val=hrs*e.hourly; const avg=days ? hrs/days : 0; grandDays += days; grandHours += hrs; grandValue += val; html += `

${e.name} — ${e.role}

Cargo: ${e.role} | Departamento: ${e.dept} | Status: ${e.status} | Valor/hora: ${fmtMoney(e.hourly)}
Dias trabalhados: ${days} | Média/dia: ${fmtHours(avg)} | Total de horas: ${fmtHours(hrs)} | Total recebido: ${fmtMoney(val)}

`; html += ``; html += reportRowsForEmployee(e,dates,true); html+=`
DataTurnoLojaInícioFimHorasTotal do diaIntervaloValor
TOTAL DE ${e.name}${fmtHours(hrs)}${fmtHours(hrs)}${fmtMoney(val)}

`; }); const grandAvg=grandDays ? grandHours/grandDays : 0; html += `

SOMATÓRIA GERAL DO RELATÓRIO

Dias trabalhados
${grandDays}
Total de horas
${fmtHours(grandHours)}
Média por dia trabalhado
${fmtHours(grandAvg)}
Total recebido
${fmtMoney(grandValue)}
`; } openPrintableWindow('Relatório', html); } function renderEmployeeListSection(){ // optional future } function reportRowsData(start,end,empId){ const dates=datesBetween(start,end); const employees=reportEmployeesForPeriod(empId,start,end).filter(Boolean); const rows=[['Data','Funcionário','Cargo','Departamento','Loja funcionário','Turno','Loja escala','Início','Fim','Horas','Intervalo','Minutos intervalo','Disponível após intervalo','Valor','Observação']]; employees.forEach(e=>{ dates.forEach(d=>{ const entries=dayEntries(e.id,d); if(entries.length===0){ rows.push([brDate(d), e.name, e.role, e.dept, e.store, 'Folga', e.store, '--', '--', '0,0', '', '', '', 'R$ 0,00', '']); }else{ entries.forEach(x=>{ const hrs=diffHours(x.start,x.end); rows.push([brDate(d), e.name, e.role, e.dept, e.store, x.shift, x.store, x.start, x.end, fmtHours(hrs), x.intervalType||'', x.intervalType ? String(x.intervalMinutes||0) : '', x.intervalType ? fmtTimeFromMinutes(entryEndWithIntervalMinutes(x)) : '', fmtMoney(hrs*e.hourly), x.obs||'']); }); } }); }); return rows; } function downloadText(filename, text, mime='text/plain'){ const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([text], {type:mime})); a.download=filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000); } function exportCsv(){ const start=document.getElementById('repStart')?.value||state.startDate; const end=document.getElementById('repEnd')?.value||addDays(state.startDate,periodLength()-1); const empId=document.getElementById('repEmployee')?.value||'all'; const rows=reportRowsData(start,end,empId); const csv=rows.map(r=>r.map(v=>'"'+String(v).replace(/"/g,'""')+'"').join(';')).join('\n'); downloadText('BLUE_SKY_SCHEDULE_'+brDate(start).replaceAll('/','-')+'_ate_'+brDate(end).replaceAll('/','-')+'.csv', '\ufeff'+csv, 'text/csv;charset=utf-8'); } async function exportJson(){ const payload={exportedAt:new Date().toISOString(), version:APP_VERSION, state}; if(window.blueSkyApi && window.blueSkyApi.exportJson){ const res=await window.blueSkyApi.exportJson(payload); alert('Exportação salva em: '+(res.file||'pasta de dados')); } else { downloadText('BLUE_SKY_SCHEDULE_BACKUP.json', JSON.stringify(payload,null,2), 'application/json'); } } async function exportMobile(){ try{ const payload={exportedAt:new Date().toISOString(), version:'BLUE_SKY_MOBILE', state}; if(window.blueSkyApi && window.blueSkyApi.exportMobile){ const res=await window.blueSkyApi.exportMobile(payload); alert('✅ Exportação para celular criada com sucesso!\n\nPasta: escalas\nArquivo: esc.mobile\nLocal: '+(res.folder||'exports/escalas')+'\n\nA pasta foi aberta automaticamente.\n\nIMPORTANTE: envie a pasta "escalas" inteira para o Cloudflare/GitHub, não somente o arquivo.'); } else { downloadText('esc.mobile', JSON.stringify(payload,null,2), 'application/json;charset=utf-8'); alert('✅ Arquivo esc.mobile baixado.\n\nCrie/envie uma pasta chamada "escalas" e coloque este arquivo dentro dela:\n\nescalas/esc.mobile'); } }catch(err){ alert('Erro ao exportar para celular: '+err.message); } } function copyPreviousWeek(){ if(!confirm('Copiar o período anterior para o período atual? Isso substituirá os horários já lançados na escala aberta.')) return; ensureSchedules(); const cur=weekDates(state.startDate); const prev=weekDates(addDays(state.startDate,-periodLength())); state.employees.forEach(e=>{ if(!state.schedules[e.id]) state.schedules[e.id]={}; cur.forEach((d,i)=>{ const src=state.schedules[e.id]?.[prev[i]]; state.schedules[e.id][d]=src ? JSON.parse(JSON.stringify(src)) : {shift:'Folga',start:'',end:'',obs:'',store:e.store||'Loja 1',entries:[]}; }); }); saveLocal(); renderAll(false); const st=document.getElementById('adminStatus'); if(st) st.textContent='Período anterior copiado com sucesso.'; } function optimizeSystem(){ cachedWeekKey=''; cachedWeekMetrics=null; document.querySelectorAll('svg').forEach(svg=>svg.innerHTML=''); renderAll(false); const st=document.getElementById('adminStatus'); if(st) st.textContent='Cache limpo e sistema otimizado.'; } function bindEvents(){ document.querySelectorAll('.menu button').forEach(btn=>btn.onclick=()=>{document.querySelectorAll('.menu button').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); document.getElementById(btn.dataset.target).scrollIntoView({behavior:'smooth'});}); document.getElementById('employeeFilter').onchange=e=>{state.selectedEmployee=e.target.value; if(state.selectedEmployee!=='all') state.editEmployeeId=state.selectedEmployee; renderAll(false);}; document.getElementById('dayFilter').onchange=e=>{const old=state.selectedDay; const next=e.target.value; if(next!==old && !validateDayBeforeNavigation(old, storeScopeForCurrentDay())){e.target.value=old; return;} state.selectedDay=next; loadEditFromState(); renderAll(false);}; document.getElementById('editShiftType').onchange=()=>{applyShiftPreset();}; document.getElementById('editShiftType2').onchange=()=>{applyShiftPreset2();}; ['editStart','editEnd','editStart2','editEnd2','storeOpenTime','storeCloseTime'].forEach(id=>{ buildTimePicker(id); setTimePickerValue(id, document.getElementById(id)?.value || '00:00'); }); document.getElementById('editStore').onchange=updateEditNumbers; document.getElementById('editStore2').onchange=updateEditNumbers; const copyDaySource=document.getElementById('copyDaySource'); if(copyDaySource) copyDaySource.onchange=e=>copySelectedDayToCurrent(e.target.value); const btnCopyEmployeeWeek=document.getElementById('btnCopyEmployeeWeek'); if(btnCopyEmployeeWeek) btnCopyEmployeeWeek.onclick=copyEmployeeWeekSchedule; const shSel=document.getElementById('storeHoursSelect'); if(shSel) shSel.onchange=()=>renderStoreHoursPanel(); const btnStoreHours=document.getElementById('btnSaveStoreHours'); if(btnStoreHours) btnStoreHours.onclick=saveStoreHours; ['storeOpenTime','storeCloseTime'].forEach(id=>{const el=document.getElementById(id); if(el) el.onblur=()=>{el.value=normalizeTimeValue(el.value)||el.value;};}); document.getElementById('btnApplyDay').onclick=applyDay; const btnDeleteSchedule=document.getElementById('btnDeleteSchedule'); if(btnDeleteSchedule) btnDeleteSchedule.onclick=deleteSelectedSchedule; document.getElementById('btnNextDay').onclick=()=>{const ok=applyDay(); if(ok===false) return; const current=state.selectedDay; if(!validateDayBeforeNavigation(current, storeScopeForCurrentDay())) return; const dates=weekDates(state.startDate); const idx=dates.indexOf(state.selectedDay); if(idx{if(!validateDayBeforeNavigation(state.selectedDay, storeScopeForCurrentDay())) return; state.startDate=addDays(state.startDate,-periodLength()); state.selectedDay=state.startDate; ensureSchedules(); renderAll();}; document.getElementById('btnNextWeek').onclick=()=>{if(!validateDayBeforeNavigation(state.selectedDay, storeScopeForCurrentDay())) return; state.startDate=addDays(state.startDate,periodLength()); state.selectedDay=state.startDate; ensureSchedules(); renderAll();}; document.getElementById('cfgEmployee').onchange=e=>{state.configEmployee=e.target.value; renderEmployeeConfig();}; document.getElementById('btnSaveEmployee').onclick=saveEmployee; document.getElementById('btnNewEmployee').onclick=newEmployee; document.getElementById('btnDeleteEmployee').onclick=deleteEmployee; document.getElementById('btnOpenWeek').onclick=openWeek; document.getElementById('periodDays').onchange=e=>{state.periodDays=Number(e.target.value)||7; state.repeatSourceStart=''; renderCalendar();}; const repeatSource=document.getElementById('repeatSource'); if(repeatSource) repeatSource.onchange=e=>{state.repeatSourceStart=e.target.value; renderCalendar(); saveLocal();}; document.getElementById('btnRepeatSchedule').onclick=repeatScheduleFromPast; document.getElementById('btnOpenReport').onclick=()=>{ const type=document.getElementById('repType').value; if(type==='employee') openReport('employee'); else if(type==='employee_final') openReport('employee_final'); else if(type==='charts') openChartsReport(); else openReport('all'); }; const btnExportCsv=document.getElementById('btnExportCsv'); if(btnExportCsv) btnExportCsv.onclick=exportCsv; const btnExportCsvAdmin=document.getElementById('btnExportCsvAdmin'); if(btnExportCsvAdmin) btnExportCsvAdmin.onclick=exportCsv; const btnExportJson=document.getElementById('btnExportJson'); if(btnExportJson) btnExportJson.onclick=exportJson; const btnExportMobile=document.getElementById('btnExportMobile'); if(btnExportMobile) btnExportMobile.onclick=exportMobile; const btnExportMobileAdmin=document.getElementById('btnExportMobileAdmin'); if(btnExportMobileAdmin) btnExportMobileAdmin.onclick=exportMobile; const btnManualBackup=document.getElementById('btnManualBackup'); if(btnManualBackup) btnManualBackup.onclick=async()=>{await createBackup('manual'); alert('Backup concluído.');}; const btnOpenDataFolder=document.getElementById('btnOpenDataFolder'); if(btnOpenDataFolder) btnOpenDataFolder.onclick=()=>window.blueSkyApi?.openDataFolder?.(); const btnCopyPreviousWeek=document.getElementById('btnCopyPreviousWeek'); if(btnCopyPreviousWeek) btnCopyPreviousWeek.onclick=copyPreviousWeek; const btnOptimizeSystem=document.getElementById('btnOptimizeSystem'); if(btnOptimizeSystem) btnOptimizeSystem.onclick=optimizeSystem; document.getElementById('btnChartsReport').onclick=openChartsReport; const chartToggle=document.getElementById('toggleChartSummary'); if(chartToggle) chartToggle.onchange=applyChartSummaryVisibility; } function renderAllDebounced(save=true){ clearTimeout(renderTimer); renderTimer=setTimeout(()=>renderAll(save), 80); } function renderAll(save=true){ ensureSchedules(); renderStoreTabs(); renderTopCards(); smartAlerts(); renderEmployeeFilter(); renderDayFilter(); renderStoreHoursPanel(); renderScheduleTable(); renderEditPanel(); renderEmployeeConfig(); renderCalendar(); renderFinance(); renderCharts(); renderReports(); document.getElementById('selectedStoreBox').textContent=state.selectedStore; if(save) saveLocal(); } async function initLicenseGate(){ if(!window.blueSkyApi || !window.blueSkyApi.getLicenseInfo) return true; const info = await window.blueSkyApi.getLicenseInfo(); applyLicenseInfoToUi(info); if(info.ok) return true; showLicenseOverlay(info); return false; } function applyLicenseInfoToUi(info){ const badge=document.getElementById('companyBadge'); if(badge){ const mode = info.permanent ? 'PRO ATIVO' : 'TESTE • ' + info.daysRemaining + ' dia(s)'; badge.textContent = 'Empresa: ' + (info.companyName || 'Cliente Teste') + ' • ' + mode; } } function showLicenseOverlay(info){ const overlay=document.getElementById('licenseOverlay'); if(!overlay) return; overlay.style.display='flex'; document.getElementById('licenseMessage').textContent = info.reason || 'Sua versão de teste expirou. Entre em contato para ativar.'; document.getElementById('licenseCompany').textContent = info.companyName || 'Cliente Teste'; document.getElementById('licenseMode').textContent = info.permanent ? 'PRO' : 'Teste bloqueado'; document.getElementById('licenseDays').textContent = info.permanent ? 'Ilimitado' : String(info.daysRemaining || 0); document.getElementById('licenseUnlockNumber').textContent = 'Nº ' + (info.nextUnlockNumber || 1); document.getElementById('licenseDeviceCode').textContent = info.deviceId || '----'; document.getElementById('licenseCompanyInput').value = info.companyName || ''; document.getElementById('btnActivateLicense').onclick = async()=>{ const key=document.getElementById('licenseKeyInput').value; const companyName=document.getElementById('licenseCompanyInput').value; const res=await window.blueSkyApi.activateLicense({key, companyName}); if(!res.ok){ alert(res.message || 'Senha inválida.'); return; } alert(res.message || 'Sistema liberado.'); location.reload(); }; document.getElementById('btnBackupLocked').onclick = async()=>{ const payload={reason:'backup_teste_bloqueado', exportedAt:new Date().toISOString(), version:APP_VERSION, state}; const res=await window.blueSkyApi.exportJson(payload); alert('Backup exportado: '+(res.file || 'pasta de dados')); }; document.getElementById('btnOpenDataFolderLocked').onclick = ()=>window.blueSkyApi.openDataFolder(); } function seedIfEmpty(){ ensureSchedules(); weekDates(state.startDate).forEach((d,i)=>{ if(i===6){ setDayData('e1',d,{shift:'Folga',start:'',end:'',obs:''}); setDayData('e2',d,{shift:'Folga',start:'',end:'',obs:''}); setDayData('e3',d,{shift:'Folga',start:'',end:'',obs:''}); setDayData('e4',d,{shift:'Folga',start:'',end:'',obs:''}); setDayData('e5',d,{shift:'Folga',start:'',end:'',obs:''}); } if(i===5){ setDayData('e3',d,{shift:'Folga',start:'',end:'',obs:''}); } }); } loadLocal(); seedIfEmpty(); initLicenseGate().then((licenseOk)=>{ if(!licenseOk) return; bindEvents(); renderAll(false); });