CA
CA OFFICE
MEISistemas CA
ERP Premium para microempresas

Gestão leve, rápida e profissional.

Controle clientes, receitas, despesas e dados da sua empresa em um painel elegante, moderno e exclusivo da Sistemas CA — com ativação automática via Mercado Pago.

PIXativação automática da assinatura
SPAnavegação fluida e muito mais rápida
1 paineltudo centralizado para o MEI

Bem-vindo ao CA Office MEI

Entre com os dados da sua assinatura para acessar o painel.

Pagamento e liberação automática via Mercado Pago. Desenvolvido por Sistemas CA
CA
CA Office MEI
Ativação da assinatura
⏳ Aguardando pagamento

Liberação automática por Pix

Após o pagamento, o seu acesso será liberado automaticamente sem envio de comprovante.

1. Pague
Use o QR Code ou o código Pix copia e cola.
2. Aguarde
Seu pagamento será validado automaticamente.
3. Acesse
Assim que o Mercado Pago aprovar, entre no painel.
Assinatura mensal
R$ 0,00
Acesso por 30 dias
Pendente

Seu QR Code Pix

Escaneie com o app do banco.

QR CODE PIX
Pix copia e cola
Aguardando geração do código Pix...
Se o pagamento já foi feito, aguarde alguns instantes e tente entrar novamente.

Resumo

Visão geral do seu negócio no mês atual.

Assinatura ativa
CA
Cliente
Conta ativa

Receitas do mês

R$ 0,00Entradas recebidas

Despesas do mês

R$ 0,00Saídas pagas

Saldo atual

R$ 0,00Receitas - despesas
💼

Clientes ativos

0Base atual cadastrada

Fluxo de caixa

Resumo dos lançamentos do mês atual.

Mês atual

Movimentações recentes

Últimos lançamentos financeiros registrados.

Data Descrição Categoria Tipo Status Valor

Total de clientes

0Base cadastrada
👥

Ativos

0Clientes com status ativo

Inativos

0Necessitam atenção
!

Com e-mail

0Contatos completos

Lista de clientes

Gerencie seus contatos e dados cadastrais.

Cliente Documento Contato Status Criado em Ações

Total de ordens

0Ordens cadastradas
🛠

Em atendimento

0Abertas ou em andamento

Aguardando cliente

0Pendentes de retorno

Concluídas

0Serviços finalizados

Ordens de serviço

Crie, acompanhe, gere PDF e compartilhe pelo WhatsApp.

OSClienteEquipamento / serviçoStatusAtualizaçãoTotalAções

Total de recibos

0Comprovantes emitidos
🧾

Emitidos no mês

0Recebimentos deste mês

Total recebido

R$ 0,00Valor dos recibos emitidos
R$

Links ativos

0Compartilhados com clientes

Recibos de pagamento

Comprovantes de valores já recebidos, com PDF e WhatsApp.

Atenção: o recibo comprova apenas o recebimento. Ele não é nota fiscal e não substitui documento fiscal quando sua emissão for obrigatória.
ReciboDataClienteReferente aFormaValorAções

Receitas do mês

R$ 0,00Lançamentos pagos

Despesas do mês

R$ 0,00Saídas pagas

Saldo atual

R$ 0,00Fluxo líquido

Contas pendentes

R$ 0,00Lançamentos em aberto
🧾

Fluxo de caixa

Evolução das receitas e despesas por dia.

Lançamentos recentes

Receitas, despesas e situação de pagamento.

Data Descrição Categoria Tipo Status Valor Ações

Minha empresa

Atualize os dados usados no painel, documentos e relatórios.

CA
Logo da empresa
Clique no botão abaixo para escolher a logo. Ela será exibida no menu lateral, no perfil e nos documentos da empresa.
Formatos: PNG, JPG ou WEBP. Tamanho máximo: 2 MB. Recomendado: imagem quadrada com fundo transparente.

Plano e assinatura

Informações da sua assinatura atual.

Assinatura ativa
Próxima validade
Renovação conforme pagamento da assinatura
Plano atual
Plano Profissional
Gestão de clientes e financeiro
`; } function fallbackPrintFinanceReport(rows, filters, totals) { const win = window.open('', '_blank', 'width=1000,height=800'); if (win) win.opener = null; if (!win) throw new Error('O navegador bloqueou a janela de impressão. Libere pop-ups e tente novamente.'); win.document.open(); win.document.write(printableReportHtml(rows, filters, totals)); win.document.close(); } async function generateFinancePdf(rows, filters) { const totals = financeReportTotals(rows); try { const jsPDF = window.jspdf?.jsPDF; if (!jsPDF) { fallbackPrintFinanceReport(rows, filters, totals); showToast('A janela de impressão foi aberta. Escolha “Salvar como PDF”.'); return; } const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); const company = state.company || {}; const companyName = company.trade_name || company.legal_name || state.user?.name || 'Minha empresa'; const companyDocument = company.document || state.user?.cpf_cnpj || ''; const pageWidth = doc.internal.pageSize.getWidth(); doc.setFillColor(6, 20, 38); doc.rect(0, 0, pageWidth, 35, 'F'); doc.setTextColor(255, 255, 255); doc.setFont('helvetica', 'bold'); doc.setFontSize(17); doc.text(companyName, 14, 15); doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.text(`Relatório financeiro · ${reportPeriodLabel(filters)}`, 14, 22); doc.text(`Gerado em ${new Date().toLocaleString('pt-BR')}${companyDocument ? ` · ${companyDocument}` : ''}`, 14, 28); const summaryY = 43; const boxWidth = (pageWidth - 28 - 9) / 4; const summary = [ ['Receitas pagas', currency.format(totals.income)], ['Despesas pagas', currency.format(totals.expense)], ['Saldo', currency.format(totals.balance)], ['Pendente', currency.format(totals.pending)] ]; summary.forEach((item, index) => { const x = 14 + index * (boxWidth + 3); doc.setDrawColor(215, 224, 235); doc.setFillColor(247, 249, 252); doc.roundedRect(x, summaryY, boxWidth, 18, 2, 2, 'FD'); doc.setTextColor(90, 105, 125); doc.setFontSize(7.3); doc.text(item[0].toUpperCase(), x + 3, summaryY + 6); doc.setTextColor(18, 33, 55); doc.setFont('helvetica', 'bold'); doc.setFontSize(10); doc.text(item[1], x + 3, summaryY + 13); doc.setFont('helvetica', 'normal'); }); const tableRows = rows.map(row => [ formatDate(row.transaction_date), String(row.description || ''), String(row.category || 'Geral'), row.type === 'income' ? 'Receita' : 'Despesa', transactionStatusLabel(row.status), `${row.type === 'expense' ? '- ' : ''}${currency.format(Number(row.value || 0))}` ]); if (typeof doc.autoTable !== 'function') { fallbackPrintFinanceReport(rows, filters, totals); showToast('A janela de impressão foi aberta. Escolha “Salvar como PDF”.'); return; } doc.autoTable({ startY: 68, head: [['Data', 'Descrição', 'Categoria', 'Tipo', 'Status', 'Valor']], body: tableRows.length ? tableRows : [['—', 'Nenhum lançamento encontrado', '—', '—', '—', '—']], theme: 'grid', styles: { font: 'helvetica', fontSize: 7.7, cellPadding: 2.4, textColor: [32, 47, 68], lineColor: [225, 231, 239], lineWidth: .2 }, headStyles: { fillColor: [10, 31, 55], textColor: [255, 255, 255], fontStyle: 'bold' }, alternateRowStyles: { fillColor: [248, 250, 252] }, columnStyles: { 0: { cellWidth: 20 }, 1: { cellWidth: 51 }, 2: { cellWidth: 31 }, 3: { cellWidth: 22 }, 4: { cellWidth: 22 }, 5: { cellWidth: 31, halign: 'right' } }, didDrawPage: data => { const pageCount = doc.internal.getNumberOfPages(); doc.setFontSize(7); doc.setTextColor(110, 124, 144); doc.text(`CA Office MEI · Página ${data.pageNumber} de ${pageCount}`, 14, doc.internal.pageSize.getHeight() - 8); } }); const filename = `relatorio-financeiro-${filters.startDate || 'inicio'}-a-${filters.endDate || 'fim'}.pdf`; doc.save(filename); showToast('PDF financeiro gerado com sucesso.'); } catch (error) { try { fallbackPrintFinanceReport(rows, filters, totals); showToast('Use a opção “Salvar como PDF” na janela de impressão.'); } catch (fallbackError) { showToast(fallbackError.message || error.message || 'Não foi possível gerar o PDF.'); } } } function renderCompany() { const company = state.company || {}; $('#companyLegalName').value = company.legal_name || state.user?.name || ''; $('#companyTradeName').value = company.trade_name || state.user?.name || ''; $('#companyDocument').value = company.document || state.user?.cpf_cnpj || ''; $('#companyEmail').value = company.email || state.user?.email || ''; $('#companyPhone').value = company.phone || state.user?.phone || ''; $('#companyZip').value = company.zip_code || ''; $('#companyAddress').value = company.address_line || ''; $('#companyCity').value = company.city || ''; $('#companyState').value = company.state || ''; refreshCompanyLogoPreview(); $('#companyLogoFileName').textContent = company.logo_data ? 'Logo salva na conta.' : 'Nenhuma logo cadastrada.'; $('#planExpiresText').textContent = state.user?.access_expires_at ? formatDate(state.user.access_expires_at) : 'Sem data'; } function renderCashChart(canvasSelector, transactions, key) { const canvas = $(canvasSelector); if (!canvas) return; const ym = new Date().toISOString().slice(0, 7); const filtered = transactions.filter(t => String(t.transaction_date || '').startsWith(ym)); const daysInMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(); const income = Array(daysInMonth).fill(0); const expense = Array(daysInMonth).fill(0); filtered.forEach(t => { const dt = String(t.transaction_date || '').slice(0, 10); const day = /^\d{4}-\d{2}-\d{2}$/.test(dt) ? Number(dt.slice(8,10)) : null; if (!day || day < 1 || day > daysInMonth) return; if (t.type === 'income') income[day - 1] += Number(t.value || 0); if (t.type === 'expense') expense[day - 1] += Number(t.value || 0); }); const balance = []; let running = 0; for (let i = 0; i < daysInMonth; i++) { running += income[i] - expense[i]; balance.push(running); } if (state.charts[key]) state.charts[key].destroy(); state.charts[key] = new Chart(canvas, { type: 'bar', data: { labels: Array.from({ length: daysInMonth }, (_, i) => String(i + 1).padStart(2, '0')), datasets: [ { type: 'bar', label: 'Receitas', data: income, backgroundColor: 'rgba(27, 211, 198, .75)', borderRadius: 8, barThickness: 16 }, { type: 'bar', label: 'Despesas', data: expense, backgroundColor: 'rgba(255, 90, 110, .78)', borderRadius: 8, barThickness: 16 }, { type: 'line', label: 'Saldo', data: balance, borderColor: '#2ca8ff', backgroundColor: 'rgba(44,168,255,.14)', tension: .36, fill: false, pointRadius: 3, pointHoverRadius: 4 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#cfe1f4', boxWidth: 10, usePointStyle: true, pointStyle: 'circle' } } }, scales: { x: { stacked: false, grid: { color: 'rgba(255,255,255,.04)' }, ticks: { color: '#8ca2bf' } }, y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,.06)' }, ticks: { color: '#8ca2bf', callback: v => currency.format(v) } } } } }); } function escapeHtml(text) { return String(text ?? '').replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m])); } async function loadSessionData() { const [me, dashboard, customers, transactions, serviceOrders, receipts] = await Promise.all([ api('/api/me'), api('/api/dashboard'), api('/api/customers'), api('/api/transactions'), api('/api/service-orders'), api('/api/receipts') ]); state.user = me.user; state.company = me.company; state.dashboard = dashboard; state.customers = Array.isArray(customers) ? customers : []; state.transactions = Array.isArray(transactions) ? transactions : []; state.serviceOrders = Array.isArray(serviceOrders) ? serviceOrders : []; state.receipts = Array.isArray(receipts) ? receipts : []; } function fillProfile() { const companyName = state.company?.trade_name || state.user?.name || 'Cliente'; $('#profileName').textContent = companyName; $('#profileSub').textContent = state.user?.access_status === 'active' ? 'Conta ativa' : (state.user?.access_status || 'Conta'); renderLogoElement($('#profileAvatar'), state.company?.logo_data || null, initials(companyName)); renderLogoElement($('#sideCompanyLogo'), state.company?.logo_data || null, initials(companyName)); $('#subscriptionBadge').className = 'badge ' + (state.user?.access_status === 'active' ? 'success' : 'warning'); $('#subscriptionBadge').textContent = state.user?.access_status === 'active' ? 'Assinatura ativa' : 'Pagamento pendente'; } async function bootstrapApp() { await loadSessionData(); fillProfile(); setScreen('appScreen'); renderDashboard(); renderCustomers(); renderServiceOrders(); renderReceipts(); renderFinance(); renderCompany(); fillCustomerSelect(); fillServiceOrderCustomerSelect(); } function fillCustomerSelect() { const select = $('#txCustomer'); const current = select.value; select.innerHTML = '' + state.customers.map(c => ``).join(''); select.value = current; } function showPendingPayment(data) { setScreen('paymentScreen'); if (!data) return; $('#paymentAmount').textContent = currency.format(Number(data.amount || 0)); $('#pixCopyPaste').textContent = data.pixCopyPaste || 'Código Pix indisponível no momento.'; $('#paymentStatusBadge').textContent = 'Pendente'; if (data.pixQrCodeBase64) { $('#pixQrImage').src = 'data:image/png;base64,' + data.pixQrCodeBase64; $('#pixQrImage').classList.remove('hidden'); $('#qrPlaceholder').classList.add('hidden'); } else { $('#pixQrImage').classList.add('hidden'); $('#qrPlaceholder').classList.remove('hidden'); } } async function pollPaymentStatus() { if (!state.payment?.paymentId) return; try { const data = await api('/api/payment-status/' + encodeURIComponent(state.payment.paymentId), { headers: {}, method: 'GET' }); if (data.access_status === 'active') { localStorage.removeItem('ca_mei_pending_payment'); state.payment = null; showToast('Pagamento confirmado. Faça login para acessar o painel.'); setScreen('authScreen'); } } catch {} } async function deleteTransaction(id) { if (!confirm('Deseja excluir este lançamento?')) return; try { await api('/api/transactions/' + encodeURIComponent(id), { method: 'DELETE' }); showToast('Lançamento excluído.'); await bootstrapApp(); setActiveView('finance'); } catch (error) { showToast(error.message); } } async function deleteCustomer(id) { if (!confirm('Deseja excluir este cliente?')) return; try { await api('/api/customers/' + encodeURIComponent(id), { method: 'DELETE' }); showToast('Cliente excluído.'); await bootstrapApp(); setActiveView('customers'); } catch (error) { showToast(error.message); } } function editCustomer(id) { const customer = state.customers.find(c => String(c.id) === String(id)); if (!customer) return; $('#customerModalTitle').textContent = 'Editar cliente'; $('#customerId').value = customer.id; $('#customerName').value = customer.name || ''; $('#customerDocument').value = customer.document || ''; $('#customerEmail').value = customer.email || ''; $('#customerPhone').value = customer.phone || ''; $('#customerActiveInput').value = Number(customer.active) === 1 ? '1' : '0'; $('#customerNotes').value = customer.notes || ''; openModal('customerModal'); } function resetCustomerForm() { $('#customerModalTitle').textContent = 'Novo cliente'; $('#customerFormModal').reset(); $('#customerId').value = ''; $('#customerActiveInput').value = '1'; } function resetTransactionForm() { $('#transactionFormModal').reset(); $('#txDate').value = new Date().toISOString().slice(0, 10); $('#txStatus').value = 'paid'; $('#txType').value = 'income'; $('#txCategory').value = 'Serviços'; } $('#loginTab').onclick = () => { $('#loginTab').classList.add('active'); $('#registerTab').classList.remove('active'); $('#loginForm').classList.remove('hidden'); $('#registerForm').classList.add('hidden'); }; $('#registerTab').onclick = () => { $('#registerTab').classList.add('active'); $('#loginTab').classList.remove('active'); $('#registerForm').classList.remove('hidden'); $('#loginForm').classList.add('hidden'); }; $('#loginForm').onsubmit = async event => { event.preventDefault(); try { const data = await api('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: $('#loginEmail').value.trim(), password: $('#loginPassword').value }) }); state.token = data.token; localStorage.setItem('ca_mei_token', state.token); if (data.user?.status === 'active') { await bootstrapApp(); } else { showPendingPayment(state.payment); } } catch (error) { showToast(error.message); } }; $('#registerForm').onsubmit = async event => { event.preventDefault(); try { const payload = { name: $('#regName').value.trim(), cpfCnpj: $('#regDocument').value.trim(), email: $('#regEmail').value.trim(), phone: $('#regPhone').value.trim(), password: $('#regPassword').value }; const data = await api('/api/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); state.payment = data; localStorage.setItem('ca_mei_pending_payment', JSON.stringify(data)); showPendingPayment(data); showToast('Conta criada. Finalize o Pix para liberar o acesso.'); } catch (error) { showToast(error.message); } }; $('#paymentLogout').onclick = () => { state.token = ''; state.user = null; localStorage.removeItem('ca_mei_token'); setScreen('authScreen'); }; $('#copyPix').onclick = async () => { try { await navigator.clipboard.writeText($('#pixCopyPaste').textContent.trim()); showToast('Código Pix copiado.'); } catch { showToast('Não foi possível copiar o código Pix.'); } }; $('#logoutBtn').onclick = async () => { try { if (state.token) await api('/api/logout', { method: 'POST' }); } catch {} state.token = ''; localStorage.removeItem('ca_mei_token'); setScreen('authScreen'); showToast('Sessão encerrada.'); }; $('#menuToggle').onclick = () => $('#sidebar').classList.toggle('open'); $$('.nav-btn[data-view]').forEach(btn => btn.onclick = () => setActiveView(btn.dataset.view)); $('#customerSearch').oninput = renderCustomers; $('#txSearch').oninput = renderFinance; $('#txFilterType').onchange = renderFinance; $('#globalSearch').oninput = () => { const value = $('#globalSearch').value.trim(); $('#customerSearch').value = value; $('#txSearch').value = value; $('#osSearch').value = value; $('#receiptSearch').value = value; if ($('#customers').classList.contains('active')) renderCustomers(); if ($('#serviceOrders').classList.contains('active')) renderServiceOrders(); if ($('#receipts').classList.contains('active')) renderReceipts(); if ($('#finance').classList.contains('active')) renderFinance(); }; $('#addCustomerBtn').onclick = () => { resetCustomerForm(); openModal('customerModal'); }; $('#quickAddCustomer').onclick = () => { resetCustomerForm(); openModal('customerModal'); }; $('#quickAddOs').onclick = () => { resetServiceOrderForm(); openModal('serviceOrderModal'); }; $('#addOsBtn').onclick = () => { resetServiceOrderForm(); openModal('serviceOrderModal'); }; $('#reloadOsBtn').onclick = async () => { await reloadServiceOrders(); showToast('Ordens de serviço atualizadas.'); }; $('#osSearch').oninput = renderServiceOrders; $('#osStatusFilter').onchange = renderServiceOrders; ['osLaborValue','osMaterialsValue','osDiscountValue'].forEach(id => $('#' + id).oninput = calculateServiceOrderTotal); $('#osStatus').onchange = () => { if ($('#osStatus').value === 'completed' && !$('#osCompletedDate').value) $('#osCompletedDate').value = new Date().toISOString().slice(0,10); }; $('#osDetailEditBtn').onclick = () => state.selectedServiceOrder && editServiceOrder(state.selectedServiceOrder.id); $('#osDetailPdfBtn').onclick = () => state.selectedServiceOrder && downloadServiceOrderPdf(state.selectedServiceOrder.id); $('#osDetailWhatsappBtn').onclick = () => state.selectedServiceOrder && shareServiceOrder(state.selectedServiceOrder.id); $('#osDetailRevokeBtn').onclick = () => state.selectedServiceOrder && revokeServiceOrderLink(state.selectedServiceOrder.id); $('#reloadCustomersBtn').onclick = async () => { await bootstrapApp(); setActiveView('customers'); showToast('Clientes atualizados.'); }; $('#quickAddTx').onclick = () => { resetTransactionForm(); fillCustomerSelect(); openModal('transactionModal'); }; $('#quickFinance').onclick = () => setActiveView('finance'); $('#quickAddReceipt').onclick = () => { fillReceiptCustomerSelect(); resetReceiptForm(); openModal('receiptModal'); }; $('#addReceiptBtn').onclick = () => { fillReceiptCustomerSelect(); resetReceiptForm(); openModal('receiptModal'); }; $('#reloadReceiptsBtn').onclick = async () => { await reloadReceipts(); showToast('Recibos atualizados.'); }; $('#receiptSearch').oninput = renderReceipts; $('#receiptDetailPdfBtn').onclick = () => state.selectedReceipt && downloadReceiptPdf(state.selectedReceipt.id); $('#receiptDetailWhatsappBtn').onclick = () => state.selectedReceipt && shareReceipt(state.selectedReceipt.id); $('#receiptDetailRevokeBtn').onclick = () => state.selectedReceipt && revokeReceiptLink(state.selectedReceipt.id); $('#addTxBtn').onclick = () => { resetTransactionForm(); fillCustomerSelect(); openModal('transactionModal'); }; $('#openReportBtn').onclick = () => openFinanceReportModal('preview'); $('#generatePdfBtn').onclick = () => openFinanceReportModal('pdf'); $('#reportPdfBtn').onclick = () => { if (!state.financeReport) return openFinanceReportModal('pdf'); generateFinancePdf(state.financeReport.rows, state.financeReport.filters); }; $('#closeReportBtn').onclick = () => { $('#financeReportPanel').classList.add('hidden'); state.financeReport = null; }; $('#refreshFinanceBtn').onclick = async () => { await bootstrapApp(); setActiveView('finance'); showToast('Lançamentos atualizados.'); }; $$('[data-close-modal]').forEach(btn => btn.onclick = () => { closeModal(btn.dataset.closeModal); if(btn.dataset.closeModal==='receiptModal'){ $('#receiptCustomer').disabled=false; $('#receiptDate').disabled=false; $('#receiptDescription').disabled=false; $('#receiptValue').disabled=false; } }); $$('.modal-backdrop').forEach(m => m.addEventListener('click', e => { if (e.target === m) m.classList.remove('show'); })); $('#customerFormModal').onsubmit = async event => { event.preventDefault(); const id = $('#customerId').value; const payload = { name: $('#customerName').value.trim(), document: $('#customerDocument').value.trim(), email: $('#customerEmail').value.trim(), phone: $('#customerPhone').value.trim(), notes: $('#customerNotes').value.trim(), active: $('#customerActiveInput').value === '1' }; try { if (id) { await api('/api/customers/' + encodeURIComponent(id), { method: 'PUT', body: JSON.stringify(payload) }); showToast('Cliente atualizado com sucesso.'); } else { await api('/api/customers', { method: 'POST', body: JSON.stringify(payload) }); showToast('Cliente cadastrado com sucesso.'); } closeModal('customerModal'); await bootstrapApp(); setActiveView('customers'); } catch (error) { showToast(error.message); } }; $('#serviceOrderForm').onsubmit = async event => { event.preventDefault(); const id = $('#osId').value; const payload = serviceOrderPayloadFromForm(); if (!payload.customerId) return showToast('Selecione o cliente da ordem de serviço.'); if (!payload.problemDescription) return showToast('Informe o problema relatado.'); try { if (id) { await api('/api/service-orders/' + encodeURIComponent(id),{method:'PUT',body:JSON.stringify(payload)}); showToast('Ordem de serviço atualizada.'); } else { await api('/api/service-orders',{method:'POST',body:JSON.stringify(payload)}); showToast('Ordem de serviço criada.'); } closeModal('serviceOrderModal'); await reloadServiceOrders(); setActiveView('serviceOrders'); } catch (error) { showToast(error.message); } }; $('#receiptForm').onsubmit = async event => { event.preventDefault(); const payload = { transactionId: $('#receiptTransactionId').value || null, customerId: $('#receiptCustomer').value, receivedDate: $('#receiptDate').value, description: $('#receiptDescription').value.trim(), amount: Number($('#receiptValue').value || 0), paymentMethod: $('#receiptPaymentMethod').value, notes: $('#receiptNotes').value.trim() || null }; try { const created = await api('/api/receipts',{method:'POST',body:JSON.stringify(payload)}); closeModal('receiptModal'); $('#receiptCustomer').disabled = false;$('#receiptDate').disabled = false;$('#receiptDescription').disabled = false;$('#receiptValue').disabled = false; showToast(`Recibo ${created.receiptNumber || ''} emitido.`); await reloadReceipts();setActiveView('receipts'); } catch(error){showToast(error.message);} }; $('#reportForm').onsubmit = event => { event.preventDefault(); const filters = getReportFilters(); if (!filters.startDate || !filters.endDate) return showToast('Informe a data inicial e a data final.'); if (filters.startDate > filters.endDate) return showToast('A data inicial não pode ser maior que a data final.'); const rows = filterTransactionsForReport(filters); closeModal('reportModal'); if (state.reportAction === 'pdf') { generateFinancePdf(rows, filters); } else { renderFinanceReport(rows, filters); showToast('Relatório financeiro gerado.'); } }; $('#transactionFormModal').onsubmit = async event => { event.preventDefault(); const payload = { type: $('#txType').value, status: $('#txStatus').value, transactionDate: $('#txDate').value, dueDate: $('#txDueDate').value || null, description: $('#txDescription').value.trim(), value: Number($('#txValue').value || 0), category: $('#txCategory').value.trim() || 'Geral', customerId: $('#txCustomer').value || null, notes: $('#txNotes').value.trim() || null }; try { await api('/api/transactions', { method: 'POST', body: JSON.stringify(payload) }); closeModal('transactionModal'); showToast('Lançamento salvo com sucesso.'); await bootstrapApp(); setActiveView('finance'); } catch (error) { showToast(error.message); } }; $('#companyLogoPreview').onclick = () => $('#companyLogoInput').click(); $('#companyLogoPreview').onkeydown = event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); $('#companyLogoInput').click(); } }; $('#companyLogoInput').onchange = event => { const file = event.target.files?.[0]; if (file) validateAndReadCompanyLogo(file); event.target.value = ''; }; $('#removeCompanyLogo').onclick = () => { state.pendingCompanyLogo = null; $('#companyLogoFileName').textContent = 'A logo será removida ao salvar as alterações.'; refreshCompanyLogoPreview(); }; $('#companyLogoPreview').ondragover = event => { event.preventDefault(); $('#companyLogoPreview').classList.add('dragover'); }; $('#companyLogoPreview').ondragleave = () => $('#companyLogoPreview').classList.remove('dragover'); $('#companyLogoPreview').ondrop = event => { event.preventDefault(); $('#companyLogoPreview').classList.remove('dragover'); const file = event.dataTransfer?.files?.[0]; if (file) validateAndReadCompanyLogo(file); }; $('#companyForm').onsubmit = async event => { event.preventDefault(); const payload = { legalName: $('#companyLegalName').value.trim(), tradeName: $('#companyTradeName').value.trim(), document: $('#companyDocument').value.trim(), email: $('#companyEmail').value.trim(), phone: $('#companyPhone').value.trim(), zipCode: $('#companyZip').value.trim(), addressLine: $('#companyAddress').value.trim(), city: $('#companyCity').value.trim(), state: $('#companyState').value.trim().toUpperCase() }; if (state.pendingCompanyLogo !== undefined) payload.logoData = state.pendingCompanyLogo; try { await api('/api/company', { method: 'PUT', body: JSON.stringify(payload) }); state.pendingCompanyLogo = undefined; showToast('Dados e logo da empresa atualizados.'); await bootstrapApp(); setActiveView('company'); } catch (error) { showToast(error.message); } }; async function init() { resetTransactionForm(); resetCustomerForm(); resetServiceOrderForm(); if (state.payment) { showPendingPayment(state.payment); pollPaymentStatus(); setInterval(pollPaymentStatus, 15000); } if (!state.token) return; try { await bootstrapApp(); } catch (error) { localStorage.removeItem('ca_mei_token'); state.token = ''; setScreen('authScreen'); showToast('Sua sessão expirou. Faça login novamente.'); } } init();