<!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>LES AVIATION | Management System (ERP • CRM • LOGÍSTICA • ESTOQUE • MRO)</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> <style> body { font-family: 'Inter', sans-serif; } @keyframes tabSlideFade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } .tab-content { display: none; } .tab-content.active { display: block; animation: tabSlideFade 0.22s cubic-bezier(0.16, 1, 0.3, 1); } .subtab-anim { animation: tabSlideFade 0.2s cubic-bezier(0.16, 1, 0.3, 1); } .les-card { background: #ffffff; border: 1px solid #e4e4e7; border-radius: 0.75rem; box-shadow: 0 1px 3px 0 rgba(0,0,0,0.04); transition: box-shadow 0.2s ease, border-color 0.2s ease; min-width: 0; overflow-wrap: break-word; } .les-card:hover { box-shadow: 0 4px 12px -2px rgba(0,0,0,0.06); } /* Menu Lateral Interativo e Recolhível */ #main-sidebar { width: 18rem; transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1); } #main-sidebar.sidebar-collapsed { width: 4.75rem; } #main-sidebar.sidebar-collapsed .sidebar-hide-collapsed { display: none !important; } #main-sidebar.sidebar-collapsed .sidebar-only-collapsed { display: flex !important; } #main-sidebar.sidebar-collapsed .nav-btn { justify-content: center; padding-left: 0.5rem; padding-right: 0.5rem; } #main-sidebar.sidebar-collapsed .nav-badge-count { display: none !important; } .nav-btn { position: relative; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 0.65rem; } .nav-btn.active-nav { background: linear-gradient(135deg, #ffffff 0%, #f4f4f5 100%); color: #09090b !important; font-weight: 700; box-shadow: 0 4px 12px rgba(0,0,0,0.25); transform: translateX(2px); } .nav-btn.active-nav::before { content: ''; position: absolute; left: -6px; top: 20%; height: 60%; width: 4px; background: #10b981; border-radius: 99px; } /* Abas Internas Modernizadas (Segmented Pill Tabs) */ .subtab-pill-bar { background: #f4f4f5; padding: 0.35rem; border-radius: 0.85rem; border: 1px solid #e4e4e7; display: inline-flex; flex-wrap: wrap; gap: 0.35rem; box-shadow: inset 0 1px 2px rgba(0,0,0,0.04); } .subtab-pill-bar-dark { background: #18181b; padding: 0.35rem; border-radius: 0.85rem; border: 1px solid #27272a; display: inline-flex; flex-wrap: wrap; gap: 0.35rem; } .subtab-btn { position: relative; display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.5rem 0.9rem; border-radius: 0.6rem; font-size: 0.72rem; font-weight: 600; color: #52525b; background: transparent; border: 1px solid transparent; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); user-select: none; } .subtab-btn:hover:not(.active-sub) { color: #09090b; background: rgba(255,255,255,0.65); transform: translateY(-1px); } .subtab-pill-bar-dark .subtab-btn { color: #a1a1aa; } .subtab-pill-bar-dark .subtab-btn:hover:not(.active-sub) { color: #ffffff; background: rgba(255,255,255,0.07); } .subtab-btn.active-sub { background: #09090b !important; color: #ffffff !important; font-weight: 700; box-shadow: 0 3px 10px -2px rgba(9, 9, 11, 0.28); transform: translateY(-1px); } .subtab-pill-bar-dark .subtab-btn.active-sub { background: #10b981 !important; color: #052e16 !important; box-shadow: 0 3px 12px -2px rgba(16, 185, 129, 0.4); } .subtab-badge { padding: 0.1rem 0.42rem; border-radius: 999px; font-size: 0.64rem; font-weight: 800; background: rgba(113,113,122,0.16); color: inherit; } .subtab-btn.active-sub .subtab-badge { background: rgba(255,255,255,0.22); color: #ffffff; } .subtab-pill-bar-dark .subtab-btn.active-sub .subtab-badge { background: rgba(5,46,22,0.2); color: #052e16; } @media print { .no-print { display: none !important; } body { background: #ffffff !important; color: #000000 !important; } main { padding: 0 !important; } .les-card { border: 1px solid #d4d4d8 !important; box-shadow: none !important; } } </style> </head> <body class="bg-zinc-100 text-zinc-900 min-h-screen flex items-center justify-center"> <!-- TELA DE AUTENTICAÇÃO DIRETA POR USUÁRIO E SENHA (LIMPA E SEM EXEMPLO DE NOME) --> <div id="login-screen" class="fixed inset-0 bg-zinc-950 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl p-8 max-w-md w-full shadow-2xl space-y-6 my-8 border border-zinc-200"> <div class="flex flex-col items-center text-center"> <svg viewBox="0 0 360 155" class="w-44 h-auto mb-3" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M22 18 V102 H98" stroke="#0A0A0A" stroke-width="15" stroke-linecap="square" stroke-linejoin="miter"/> <line x1="128" y1="25.5" x2="218" y2="25.5" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="60" x2="218" y2="60" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="94.5" x2="218" y2="94.5" stroke="#0A0A0A" stroke-width="15"/> <path d="M332 25.5 H275 C255 25.5 246 35 246 43.5 C246 54 256 60 274 60 H308 C328 60 338 68 338 78.5 C338 89 327 94.5 306 94.5 H244" stroke="#0A0A0A" stroke-width="15"/> <text x="180" y="142" text-anchor="middle" fill="#0A0A0A" font-family="Inter, sans-serif" font-size="23" font-weight="500" letter-spacing="14">AVIATION</text> </svg> <h2 class="text-xs font-extrabold uppercase tracking-[0.2em] text-zinc-900">LES Aviation Management System</h2> <p class="text-[11px] text-zinc-500 mt-1">Acesso Corporativo Integrado</p> </div> <form onsubmit="fazerLogin(event)" class="space-y-4"> <div> <label class="block text-xs font-semibold text-zinc-700 mb-1">Nome de Usuário</label> <input type="text" id="login-user" required autocomplete="username" placeholder="Digite seu usuário" class="w-full border border-zinc-300 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-zinc-950"> </div> <div> <label class="block text-xs font-semibold text-zinc-700 mb-1">Senha</label> <input type="password" id="login-pass" required autocomplete="current-password" placeholder="••••••••" class="w-full border border-zinc-300 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-zinc-950"> </div> <button type="submit" id="btn-login-submit" class="w-full bg-zinc-950 hover:bg-zinc-800 text-white font-semibold py-2.5 rounded-lg text-sm transition shadow-md"> Entrar no Sistema </button> </form> <div class="pt-2 border-t border-zinc-100 text-center flex items-center justify-center gap-2"> <span id="login-cloud-dot" class="w-2 h-2 rounded-full bg-amber-400 animate-pulse"></span> <p id="login-cloud-text" class="text-[10px] text-zinc-500 uppercase tracking-widest">Conectando à base em nuvem LES...</p> </div> </div> </div> <!-- MODAL DE TROCA DE SENHA DO USUÁRIO --> <div id="modal-trocar-senha" class="hidden fixed inset-0 bg-black/75 z-50 flex items-center justify-center p-4"> <div class="bg-white rounded-2xl max-w-md w-full p-6 space-y-4 shadow-2xl border border-zinc-200"> <div class="flex justify-between items-center border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-600 font-bold block">Credencial de Acesso</span> <h3 class="text-sm font-extrabold text-zinc-950">Alterar Minha Senha</h3> </div> <button type="button" onclick="fecharModalTrocarSenha()" class="text-zinc-400 hover:text-zinc-900 text-lg font-bold">×</button> </div> <form onsubmit="salvarNovaSenhaUsuario(event)" class="space-y-3 text-xs"> <div> <label class="font-semibold block text-zinc-700 mb-1">Usuário</label> <input type="text" id="troca-senha-usuario" readonly class="w-full border border-zinc-200 bg-zinc-100 rounded-lg px-3 py-2 font-bold text-zinc-800"> </div> <div> <label class="font-semibold block text-zinc-700 mb-1">Senha Atual *</label> <input type="password" id="troca-senha-atual" required placeholder="Digite sua senha atual" class="w-full border border-zinc-300 rounded-lg px-3 py-2"> </div> <div> <label class="font-semibold block text-zinc-700 mb-1">Nova Senha *</label> <input type="password" id="troca-senha-nova" required placeholder="Digite a nova senha" class="w-full border border-zinc-300 rounded-lg px-3 py-2"> </div> <div> <label class="font-semibold block text-zinc-700 mb-1">Confirmar Nova Senha *</label> <input type="password" id="troca-senha-confirma" required placeholder="Repita a nova senha" class="w-full border border-zinc-300 rounded-lg px-3 py-2"> </div> <div class="flex justify-end gap-2 pt-2 border-t"> <button type="button" onclick="fecharModalTrocarSenha()" class="px-4 py-2 bg-zinc-200 hover:bg-zinc-300 text-zinc-800 rounded-lg font-semibold">Cancelar</button> <button type="submit" class="px-5 py-2 bg-zinc-950 hover:bg-zinc-800 text-white rounded-lg font-bold shadow"> <i class="fa-solid fa-key mr-1.5 text-emerald-400"></i> Salvar Nova Senha </button> </div> </form> </div> </div> <!-- MODAL VISUALIZADOR INTEGRADO DE MANUAIS TÉCNICOS E PDFs HOMOLOGADOS --> <div id="modal-visualizar-manual" class="hidden fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl max-w-4xl w-full p-6 space-y-4 shadow-2xl my-6 border border-zinc-200"> <div class="flex flex-wrap justify-between items-center gap-3 border-b pb-3"> <div> <span id="modal-man-status-badge" class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-emerald-100 text-emerald-900 inline-block mb-1">Manual Técnico</span> <h3 id="modal-man-titulo" class="text-base font-extrabold text-zinc-950">Visualizador de Manual Técnico</h3> <p id="modal-man-subtitulo" class="text-[11px] text-zinc-500 mt-0.5"></p> </div> <div class="flex flex-wrap items-center gap-2"> <button type="button" id="btn-modal-man-nova-aba" class="bg-zinc-950 hover:bg-zinc-800 text-white px-3.5 py-2 rounded-xl text-xs font-bold shadow flex items-center gap-1.5"> <i class="fa-solid fa-up-right-from-square text-emerald-400"></i> Abrir / Imprimir em Nova Guia </button> <button type="button" id="btn-modal-man-download" class="bg-sky-600 hover:bg-sky-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold shadow flex items-center gap-1.5"> <i class="fa-solid fa-download"></i> Baixar PDF </button> <label class="cursor-pointer bg-amber-100 hover:bg-amber-200 text-amber-950 border border-amber-300 px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-1.5"> <i class="fa-solid fa-file-arrow-up"></i> Anexar / Atualizar PDF <input type="file" id="input-modal-man-update-pdf" accept=".pdf,image/*" class="hidden"> </label> <button type="button" onclick="fecharModalVisualizarManual()" class="text-zinc-400 hover:text-zinc-900 text-xl font-bold px-2">×</button> </div> </div> <div id="modal-man-metadados" class="grid grid-cols-1 md:grid-cols-4 gap-3 bg-zinc-50 p-3.5 rounded-xl border text-xs"></div> <div id="modal-man-viewer-container" class="w-full rounded-xl overflow-hidden border border-zinc-300 bg-zinc-100 min-h-[420px] flex items-center justify-center"></div> <div class="flex justify-end pt-2 border-t text-xs"> <button type="button" onclick="fecharModalVisualizarManual()" class="px-5 py-2 bg-zinc-900 text-white rounded-lg font-semibold">Fechar Visualizador</button> </div> </div> </div> <!-- MODAL DE AVISO GERAL DO MATHEUS (APARECE UMA VEZ AO ABRIR E SOME APÓS DAR CIÊNCIA) --> <div id="modal-aviso-ciencia" class="hidden fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4"> <div class="bg-white rounded-2xl max-w-lg w-full p-6 space-y-5 shadow-2xl border-2 border-zinc-900"> <div class="flex items-start justify-between gap-3 border-b pb-3"> <div> <span id="aviso-ciencia-prioridade" class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-amber-500 text-zinc-950 inline-block mb-1">Comunicado Oficial</span> <h3 id="aviso-ciencia-titulo" class="text-base font-extrabold text-zinc-950">Aviso Geral da Diretoria</h3> <p id="aviso-ciencia-meta" class="text-[11px] text-zinc-500 mt-0.5">Enviado por Matheus Azevedo</p> </div> <div class="w-10 h-10 rounded-xl bg-zinc-950 text-amber-400 flex items-center justify-center shrink-0 shadow"> <i class="fa-solid fa-bullhorn text-base"></i> </div> </div> <div id="aviso-ciencia-texto" class="bg-zinc-50 border border-zinc-200 rounded-xl p-4 text-xs text-zinc-800 leading-relaxed whitespace-pre-line"></div> <div class="flex items-center justify-between gap-3 pt-2 border-t text-xs"> <span class="text-[11px] text-zinc-500 italic">Após confirmar ciência, este aviso não aparecerá novamente.</span> <button type="button" onclick="confirmarCienciaAvisoGeral()" class="px-6 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-extrabold shadow-md transition flex items-center gap-2 shrink-0"> <i class="fa-solid fa-check-double"></i> Estou Ciente </button> </div> </div> </div> <!-- MODAL EXCLUSIVO DO MATHEUS PARA ENVIAR NOVO AVISO GERAL A TODOS --> <div id="modal-aviso-matheus-admin" class="hidden fixed inset-0 bg-black/75 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl max-w-2xl w-full p-6 space-y-5 shadow-2xl my-8 border border-zinc-200"> <div class="flex justify-between items-center border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-amber-600 font-bold block"><i class="fa-solid fa-crown mr-1"></i> Exclusivo Perfil Master • Matheus Azevedo</span> <h3 class="text-base font-extrabold text-zinc-950">Enviar Aviso Geral para Todos os Perfis</h3> <p class="text-[11px] text-zinc-500">O aviso enviado aparecerá automaticamente na tela de cada usuário ao abrir o sistema até que ele clique em "Estou Ciente".</p> </div> <button type="button" onclick="fecharModalGerenciarAvisoMatheus()" class="text-zinc-400 hover:text-zinc-900 text-xl font-bold px-2">×</button> </div> <form onsubmit="enviarComunicadoMatheus(event)" class="space-y-3 text-xs bg-zinc-50 p-4 rounded-xl border"> <div class="grid grid-cols-1 md:grid-cols-3 gap-3"> <div class="md:col-span-2"> <label class="font-bold block mb-1 text-zinc-700">Título do Aviso Geral *</label> <input type="text" id="mural-titulo" required placeholder="Ex: Alinhamento Operacional da Semana" class="w-full border rounded-lg px-3 py-2 bg-white"> </div> <div> <label class="font-bold block mb-1 text-zinc-700">Nível de Destaque *</label> <select id="mural-prioridade" class="w-full border rounded-lg px-3 py-2 bg-white font-semibold"> <option value="Informativo">Informativo</option> <option value="Importante" selected>Importante</option> <option value="Urgente">Urgente</option> </select> </div> </div> <div> <label class="font-bold block mb-1 text-zinc-700">Mensagem para todos os usuários *</label> <input type="text" id="mural-texto" required placeholder="Digite a mensagem que será exibida na janela de ciência para todos os colaboradores..." class="w-full border rounded-lg px-3 py-2 bg-white"> </div> <div class="flex justify-end pt-1"> <button type="submit" class="bg-zinc-950 hover:bg-zinc-800 text-white font-bold px-5 py-2.5 rounded-lg shadow"> <i class="fa-solid fa-paper-plane mr-1.5 text-emerald-400"></i> Publicar Novo Aviso Geral (Solicitar Ciência) </button> </div> </form> <div class="space-y-2 text-xs"> <h4 class="font-bold uppercase text-zinc-700 text-[11px]">Aviso Geral Atual & Controle de Ciência da Equipe</h4> <div id="painel-status-ciencia-matheus" class="p-4 rounded-xl border bg-white space-y-2"></div> </div> <div class="flex justify-end pt-2 border-t text-xs"> <button type="button" onclick="fecharModalGerenciarAvisoMatheus()" class="px-5 py-2 bg-zinc-200 hover:bg-zinc-300 text-zinc-800 rounded-lg font-semibold">Fechar</button> </div> </div> </div> <!-- MODAL DE STATUS DE ENTREGA E RASTREABILIDADE (ACIONADO PELA ABA VENDAS) --> <div id="modal-status-rastreabilidade-venda" class="hidden fixed inset-0 bg-black/75 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl max-w-2xl w-full p-6 space-y-5 shadow-2xl my-8 border border-zinc-200"> <div class="flex justify-between items-center border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Consulta Comercial • Status de Entrega & Rastreabilidade</span> <h3 id="modal-rv-titulo" class="text-base font-extrabold text-zinc-950">Status do Pedido</h3> </div> <button type="button" onclick="fecharJanelaRastreabilidadeVenda()" class="text-zinc-400 hover:text-zinc-900 text-xl font-bold px-2">×</button> </div> <div id="modal-rv-conteudo" class="space-y-4 text-xs"></div> <div class="flex justify-between items-center pt-2 border-t text-xs"> <span class="text-[11px] text-zinc-400 italic"><i class="fa-solid fa-lock mr-1"></i>Atualização de etapa logística exclusiva do perfil de Logística.</span> <button type="button" onclick="fecharJanelaRastreabilidadeVenda()" class="px-5 py-2 bg-zinc-950 text-white rounded-lg font-semibold">Fechar Janela</button> </div> </div> </div> <!-- MODAL PRÓPRIO DE CANCELAMENTO / RECUSA --> <div id="modal-cancelamento" class="hidden fixed inset-0 bg-black/75 z-50 flex items-center justify-center p-4"> <div class="bg-white rounded-xl max-w-md w-full p-6 space-y-4 shadow-2xl"> <div class="flex justify-between items-center border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-rose-600 font-bold block">Governança & Rastreabilidade</span> <h3 id="modal-canc-titulo" class="text-sm font-extrabold text-zinc-950">Confirmar Cancelamento / Recusa</h3> </div> <button type="button" onclick="fecharModalCancelamento()" class="text-zinc-400 hover:text-zinc-900 text-lg font-bold">×</button> </div> <div class="space-y-2 text-xs"> <label class="font-semibold block text-zinc-700">Informe o motivo da ação (será gravado na auditoria):</label> <input type="text" id="modal-canc-motivo" value="Solicitação comercial / revisão de escopo" class="w-full border border-zinc-300 rounded-lg px-3 py-2 text-xs"> </div> <div class="flex justify-end gap-2 pt-2 border-t"> <button type="button" onclick="fecharModalCancelamento()" class="px-4 py-2 bg-zinc-200 hover:bg-zinc-300 text-zinc-800 rounded-lg text-xs font-semibold">Voltar</button> <button type="button" onclick="confirmarAcaoModalCancelamento()" class="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-lg text-xs font-bold">Confirmar e Registrar</button> </div> </div> </div> <!-- MODAL DE ANEXOS E INFORMAÇÕES COMERCIAIS DO PEDIDO (EXCLUSIVO PERFIL DE VENDAS P/ COTAÇÃO FABRICANTE E COMPROVANTE) --> <div id="modal-po-anexos" class="hidden fixed inset-0 bg-black/75 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl max-w-3xl w-full p-6 space-y-5 shadow-2xl my-8"> <div class="flex justify-between items-center border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Dossiê Comercial do Pedido • Alimentação pelo Perfil de Vendas</span> <h3 id="modal-po-titulo" class="text-base font-extrabold text-zinc-950 font-mono">PO-0000</h3> <p class="text-[11px] text-zinc-500 mt-0.5">O perfil de <b>Vendas / Comercial</b> (e Diretoria) é responsável por inserir os dados e anexos da <b>Cotação do Fabricante</b> e do <b>Comprovante de Pagamento</b>.</p> </div> <button type="button" onclick="fecharModalAnexosPO()" class="text-zinc-400 hover:text-zinc-900 text-lg font-bold">×</button> </div> <div id="modal-po-slots" class="space-y-4 text-xs"></div> <div class="flex justify-end gap-2 pt-2 border-t"> <button type="button" onclick="fecharModalAnexosPO()" class="px-5 py-2 bg-zinc-950 hover:bg-zinc-800 text-white rounded-lg text-xs font-semibold">Concluir e Fechar</button> </div> </div> </div> <!-- MODAL DE ATUALIZAÇÃO DE RASTREAMENTO LOGÍSTICO DO PEDIDO (TRACKING) --> <div id="modal-tracking-po" class="hidden fixed inset-0 bg-black/75 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl max-w-3xl w-full p-6 space-y-5 shadow-2xl my-8"> <div class="flex justify-between items-center border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Controle Logístico & Aduaneiro • Rastreamento End-to-End</span> <h3 id="modal-trk-titulo" class="text-base font-extrabold text-zinc-950 font-mono">Rastreamento PO-0000</h3> </div> <button type="button" onclick="fecharModalTrackingPO()" class="text-zinc-400 hover:text-zinc-900 text-lg font-bold">×</button> </div> <div id="modal-trk-stepper" class="py-2"></div> <form onsubmit="salvarAtualizacaoTrackingPO(event)" class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs bg-zinc-50 p-4 rounded-xl border"> <input type="hidden" id="trk-po-numero"> <div class="md:col-span-2"> <label class="font-bold block mb-1 text-zinc-800">Etapa / Processo Atual do Pedido *</label> <select id="trk-etapa" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="1_PEDIDO_RECEBIDO">1. Pedido Recebido / Em Validação de Dossiê</option> <option value="2_PO_EMITIDO">2. P.O. Emitido ao Fabricante Internacional</option> <option value="3_EM_PRODUCAO">3. Em Produção / Separação no Fabricante</option> <option value="4_DESPACHADO_ORIGEM">4. Já Despachado da Fábrica / Pronto p/ Embarque</option> <option value="5_TRANSITO_INTERNACIONAL">5. Em Trânsito Internacional (Aéreo / Marítimo)</option> <option value="6_CHEGADA_BRASIL">6. Chegada no Brasil / Recinto Alfandegado</option> <option value="7_EM_DESEMBARACO">7. Em Processo de Desembaraço Aduaneiro (RFB / ANAC)</option> <option value="8_DESEMBARACADO">8. Já Desembaraçado / Nacionalizado e Liberado</option> <option value="9_TRANSPORTE_NACIONAL">9. Em Transporte Nacional (Rumo à Base LES / Cliente)</option> <option value="10_ENTREGUE">10. Entregue ao Cliente / Pedido Finalizado</option> </select> </div> <div> <label class="font-bold block mb-1 text-zinc-800">Onde está o pedido agora? (Local) *</label> <input type="text" id="trk-local" required placeholder="Ex: Graz (Áustria) / Aeroporto VCP / Itajaí-SC" class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> <div> <label class="font-bold block mb-1 text-zinc-800">Em qual processo específico? *</label> <input type="text" id="trk-processo" required placeholder="Ex: Conferência DI, Aguardando voo, Liberado RFB" class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> <div> <label class="font-bold block mb-1 text-zinc-800">Código Rastreio / AWB / BL</label> <input type="text" id="trk-awb" placeholder="Ex: AWB LH-8849201 / DHL" class="w-full border rounded-lg px-2.5 py-2 bg-white font-mono"> </div> <div> <label class="font-bold block mb-1 text-zinc-800">Nº DI / DUIMP & Canal Aduaneiro</label> <input type="text" id="trk-di" placeholder="Ex: DI 26/194022-1 • Canal Verde" class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> <div> <label class="font-bold block mb-1 text-zinc-800">Previsão Atualizada (ETA)</label> <input type="date" id="trk-eta" class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> <div class="md:col-span-2"> <label class="font-bold block mb-1 text-zinc-800">Observação / Ocorrência desta Atualização</label> <input type="text" id="trk-obs" placeholder="Detalhes sobre despacho, voo, receita federal ou transportadora..." class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> <div class="md:col-span-3 flex justify-end gap-2 pt-2"> <button type="button" onclick="fecharModalTrackingPO()" class="px-4 py-2 bg-zinc-200 hover:bg-zinc-300 text-zinc-800 rounded-lg font-semibold">Fechar</button> <button type="submit" class="px-5 py-2 bg-sky-600 hover:bg-sky-700 text-white rounded-lg font-bold shadow"> <i class="fa-solid fa-location-crosshairs mr-1.5"></i> Salvar Status de Rastreamento </button> </div> </form> <div class="space-y-2"> <h4 class="text-xs font-bold uppercase text-zinc-700">Histórico Cronológico de Eventos do Pedido</h4> <div id="modal-trk-historico" class="max-h-48 overflow-y-auto space-y-2 pr-1"></div> </div> </div> </div> <!-- MODAL DE JANELA INDIVIDUAL DE SUBTAREFA DE P&D / DESENVOLVIMENTO (v1.24) --> <div id="modal-subtarefa-dev" class="hidden fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4 overflow-y-auto"> <div class="bg-white rounded-2xl max-w-2xl w-full p-6 space-y-5 shadow-2xl my-8 border border-zinc-200"> <div class="flex justify-between items-start gap-3 border-b pb-3"> <div> <div class="flex flex-wrap items-center gap-2 mb-1"> <span id="modal-subdev-task-badge" class="font-mono font-black text-[10px] bg-zinc-950 text-emerald-400 px-2.5 py-0.5 rounded">DEV-0000</span> <span id="modal-subdev-etapa-badge" class="px-2.5 py-0.5 rounded text-[10px] font-bold bg-zinc-100 text-zinc-700 border border-zinc-200">Etapa</span> <span id="modal-subdev-resp-badge" class="text-[10px] font-semibold text-emerald-800 bg-emerald-50 border border-emerald-200 px-2 py-0.5 rounded">Responsáveis</span> </div> <h3 id="modal-subdev-header-titulo" class="text-base font-extrabold text-zinc-950">Janela da Subtarefa de P&D</h3> <p id="modal-subdev-projeto-sub" class="text-[11px] text-zinc-500 mt-0.5">Detalhes, valor estimado, anotações e documentação da etapa (anexo opcional para concluir).</p> </div> <button type="button" onclick="fecharJanelaSubtarefaDev()" class="text-zinc-400 hover:text-zinc-900 text-2xl font-bold px-2 leading-none">×</button> </div> <div class="space-y-4 text-xs"> <input type="hidden" id="modal-subdev-tarefa-id" value=""> <input type="hidden" id="modal-subdev-sub-id" value=""> <div class="grid grid-cols-1 md:grid-cols-12 gap-3"> <div class="md:col-span-8"> <label class="font-bold block mb-1 text-zinc-800">Título / Descrição da Subtarefa *</label> <input type="text" id="modal-subdev-titulo" required placeholder="Nome da subtarefa..." class="w-full border border-zinc-300 rounded-lg px-3 py-2 font-bold text-zinc-900 bg-white"> </div> <div class="md:col-span-4"> <label class="font-bold block mb-1 text-emerald-900">Valor Estimado desta Etapa (R$)</label> <input type="number" step="0.01" min="0" id="modal-subdev-valor" value="0" placeholder="0.00" class="w-full border-2 border-emerald-500 rounded-lg px-3 py-2 font-mono font-extrabold text-emerald-950 bg-emerald-50/40"> </div> </div> <div class="p-3.5 rounded-xl border bg-zinc-50 flex flex-wrap justify-between items-center gap-3"> <label class="inline-flex items-center gap-2.5 cursor-pointer select-none"> <input type="checkbox" id="modal-subdev-concluida" class="w-5 h-5 accent-emerald-600 rounded cursor-pointer"> <div> <span class="font-extrabold text-zinc-900 block text-xs">Marcar Subtarefa como Concluída</span> <span class="text-[10px] text-zinc-500 block">Não é obrigatório anexar documento para concluir; você pode registrar apenas o texto abaixo ou concluir diretamente.</span> </div> </label> <span id="modal-subdev-status-pill" class="px-3 py-1 rounded-full text-[10px] font-extrabold uppercase bg-amber-100 text-amber-900">Em Andamento</span> </div> <div> <label class="font-bold block mb-1 text-zinc-800"><i class="fa-solid fa-align-left mr-1 text-sky-600"></i> Caixa de Texto da Subtarefa (Relatório / Observações de Cumprimento da Etapa)</label> <div id="modal-subdev-texto-mount"></div> </div> <div class="p-4 rounded-xl border border-zinc-200 bg-zinc-50 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2"> <div> <span class="font-extrabold text-zinc-900 block text-xs"><i class="fa-solid fa-paperclip mr-1 text-emerald-600"></i> Documentação para Cumprimento da Etapa (Anexo Opcional)</span> <span class="text-[10px] text-zinc-500">Anexe PDFs, imagens, relatórios ou comprovantes técnicos desta subtarefa se desejar.</span> </div> <label class="cursor-pointer bg-zinc-950 hover:bg-zinc-800 text-white px-3.5 py-2 rounded-lg text-[11px] font-bold shadow-sm transition inline-flex items-center gap-1.5"> <i class="fa-solid fa-upload text-emerald-400"></i> Anexar Documento(s) <input type="file" id="modal-subdev-file-input" accept=".pdf,image/*,.doc,.docx,.xls,.xlsx,.txt" multiple onchange="anexarDocumentoNaSubtarefaModal(event)" class="hidden"> </label> </div> <div id="modal-subdev-anexos-lista" class="space-y-2"></div> </div> </div> <div class="flex flex-wrap justify-between items-center gap-2 pt-3 border-t text-xs"> <button type="button" onclick="fecharJanelaSubtarefaDev()" class="px-4 py-2 bg-zinc-200 hover:bg-zinc-300 text-zinc-800 rounded-lg font-semibold">Cancelar / Fechar</button> <div class="flex items-center gap-2"> <button type="button" onclick="salvarDadosJanelaSubtarefaDev(false)" class="px-4 py-2 bg-sky-600 hover:bg-sky-700 text-white rounded-lg font-bold shadow"> <i class="fa-solid fa-floppy-disk mr-1"></i> Salvar Dados da Subtarefa </button> <button type="button" onclick="salvarDadosJanelaSubtarefaDev(true)" class="px-5 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-extrabold shadow"> <i class="fa-solid fa-check-double mr-1"></i> Salvar & Concluir Etapa </button> </div> </div> </div> </div> <!-- CONTAINER PRINCIPAL DA APLICAÇÃO --> <div id="app-container" class="hidden min-h-screen flex w-full"> <!-- MENU LATERAL RECOLHÍVEL (COLLAPSIBLE SIDEBAR) --> <aside id="main-sidebar" class="bg-zinc-950 text-white flex flex-col no-print shrink-0 border-r border-zinc-800 select-none relative"> <div class="bg-white text-zinc-950 p-4 border-b border-zinc-200 flex flex-col items-center justify-center relative group min-h-[84px]"> <!-- Botão de Recolher / Expandir Menu Lateral --> <button type="button" onclick="toggleSidebarCollapse()" title="Recolher / Expandir Menu Lateral" class="absolute top-2.5 right-2.5 w-7 h-7 rounded-lg bg-zinc-100 hover:bg-zinc-900 text-zinc-700 hover:text-white flex items-center justify-center transition shadow-sm border border-zinc-200 z-10"> <i id="sidebar-toggle-icon" class="fa-solid fa-angles-left text-xs"></i> </button> <!-- Logo Expandido --> <div id="logo-container-sidebar" class="w-full flex flex-col items-center sidebar-hide-collapsed"> <svg viewBox="0 0 360 155" class="w-36 h-auto" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M22 18 V102 H98" stroke="#0A0A0A" stroke-width="15" stroke-linecap="square" stroke-linejoin="miter"/> <line x1="128" y1="25.5" x2="218" y2="25.5" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="60" x2="218" y2="60" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="94.5" x2="218" y2="94.5" stroke="#0A0A0A" stroke-width="15"/> <path d="M332 25.5 H275 C255 25.5 246 35 246 43.5 C246 54 256 60 274 60 H308 C328 60 338 68 338 78.5 C338 89 327 94.5 306 94.5 H244" stroke="#0A0A0A" stroke-width="15"/> <text x="180" y="142" text-anchor="middle" fill="#0A0A0A" font-family="Inter, sans-serif" font-size="23" font-weight="500" letter-spacing="14">AVIATION</text> </svg> </div> <!-- Monograma Compacto quando Recolhido --> <div class="hidden sidebar-only-collapsed flex-col items-center justify-center pt-5"> <span class="text-sm font-black tracking-widest text-zinc-950 border-2 border-zinc-950 px-1.5 py-0.5 rounded">LES</span> </div> <img id="custom-logo-sidebar" class="max-h-16 w-auto hidden object-contain sidebar-hide-collapsed" alt="LES Aviation"> <label class="mt-1 cursor-pointer text-[10px] text-zinc-400 hover:text-zinc-900 transition opacity-0 group-hover:opacity-100 sidebar-hide-collapsed"> <i class="fa-solid fa-camera mr-1"></i> Alterar Logo <input type="file" accept="image/*" class="hidden" onchange="carregarLogoArquivo(event)"> </label> </div> <!-- Perfil do Usuário Logado --> <div class="px-4 py-3 bg-zinc-900 border-b border-zinc-800 flex items-center justify-between gap-1"> <div class="truncate pr-1 sidebar-hide-collapsed"> <span id="user-badge" class="text-[11px] text-emerald-400 font-bold block truncate">USUÁRIO</span> <span id="user-sub-badge" class="text-[10px] uppercase tracking-wider text-zinc-400 block truncate">ÁREA</span> </div> <div class="flex items-center gap-1 shrink-0 mx-auto md:mx-0"> <button onclick="abrirModalTrocarSenha()" title="Trocar Minha Senha" class="text-[11px] bg-zinc-800 hover:bg-zinc-700 text-zinc-200 px-2 py-1.5 rounded-lg transition"> <i class="fa-solid fa-key"></i> </button> <button onclick="fazerLogout()" title="Sair do Sistema" class="text-[11px] bg-zinc-800 hover:bg-rose-600 text-zinc-200 hover:text-white px-2.5 py-1.5 rounded-lg transition flex items-center gap-1"> <i class="fa-solid fa-right-from-bracket"></i> <span class="sidebar-hide-collapsed">Sair</span> </button> </div> </div> <!-- Navegação Principal dos Módulos --> <nav id="sidebar-nav" class="flex-1 p-3 space-y-1.5 text-xs font-medium overflow-y-auto"></nav> <!-- Rodapé do Menu Lateral (Sincronizar / Exportar / Importar EXCLUSIVOS DO PERFIL MASTER DO MATHEUS) --> <div class="p-3 border-t border-zinc-800 space-y-2 text-xs"> <div id="sidebar-master-sync-controls" class="hidden space-y-2"> <button onclick="sincronizarComNuvemAgora(true)" title="Sincronizar Nuvem Agora (Exclusivo Master)" class="w-full py-2 px-3 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-semibold transition flex items-center justify-center gap-2 shadow"> <i class="fa-solid fa-rotate"></i> <span class="sidebar-hide-collapsed">Sincronizar Nuvem</span> </button> <div class="grid grid-cols-2 gap-1.5 sidebar-hide-collapsed"> <button onclick="exportarJSON()" class="py-1.5 px-2 bg-zinc-900 hover:bg-zinc-800 text-zinc-300 rounded-lg border border-zinc-800 text-[11px]"> <i class="fa-solid fa-download mr-1"></i> Exportar </button> <label class="py-1.5 px-2 bg-zinc-900 hover:bg-zinc-800 text-zinc-300 rounded-lg border border-zinc-800 text-[11px] text-center cursor-pointer"> <i class="fa-solid fa-upload mr-1"></i> Importar <input type="file" accept=".json" class="hidden" onchange="importarBackupJSON(event)"> </label> </div> </div> <!-- ÚNICO LOCAL COM A VERSÃO DO SISTEMA --> <div class="flex items-center justify-between text-[10px] text-zinc-500 px-1 sidebar-hide-collapsed"> <span>LAMS</span> <span class="font-mono font-bold text-emerald-400">Versão 1.24</span> </div> </div> </aside> <!-- ÁREA PRINCIPAL --> <main class="flex-1 flex flex-col min-w-0 overflow-y-auto bg-zinc-100"> <header class="bg-white border-b border-zinc-200 px-6 py-3.5 flex justify-between items-center no-print sticky top-0 z-20 shadow-sm"> <div class="flex items-center gap-3"> <button type="button" onclick="toggleSidebarCollapse()" title="Alternar Menu Lateral" class="w-9 h-9 rounded-lg bg-zinc-100 hover:bg-zinc-200 text-zinc-800 flex items-center justify-center transition border border-zinc-200"> <i class="fa-solid fa-bars-staggered text-sm"></i> </button> <div> <span class="text-[10px] font-bold uppercase tracking-[0.26em] text-zinc-400 block">LES AVIATION MANAGEMENT SYSTEM</span> <h1 id="page-title" class="text-base md:text-lg font-extrabold text-zinc-950 tracking-tight">Dashboard</h1> </div> </div> <div class="flex items-center gap-2.5"> <!-- Botão Exclusivo do Matheus para Publicar Aviso Geral --> <button type="button" id="btn-header-aviso-matheus" onclick="abrirModalGerenciarAvisoMatheus()" class="hidden bg-amber-50 hover:bg-amber-100 text-amber-950 border border-amber-300 px-3 py-1.5 rounded-xl text-xs font-bold items-center gap-1.5 transition shadow-sm"> <i class="fa-solid fa-bullhorn text-amber-600"></i> <span>Enviar Aviso Geral</span> </button> <!-- Botão de Troca de Senha --> <button type="button" onclick="abrirModalTrocarSenha()" title="Alterar Minha Senha" class="bg-zinc-100 hover:bg-zinc-200 text-zinc-800 border border-zinc-200 px-3 py-1.5 rounded-xl text-xs font-semibold flex items-center gap-1.5 transition"> <i class="fa-solid fa-key text-amber-600"></i> <span class="hidden lg:inline">Trocar Senha</span> </button> <div class="hidden sm:flex items-center gap-2 bg-zinc-100 border border-zinc-200 px-3 py-1.5 rounded-full"> <span id="cloud-status-dot" class="w-2.5 h-2.5 rounded-full bg-emerald-500"></span> <span id="cloud-status-label" class="text-[11px] font-semibold text-zinc-700">Nuvem Sincronizada</span> </div> <div class="text-right hidden md:block"> <p id="header-perfil-nome" class="text-xs font-bold text-zinc-900">Usuário Autenticado</p> <p id="header-permissao-desc" class="text-[11px] text-zinc-500">Acesso Corporativo</p> </div> <div id="header-avatar" class="h-9 w-9 rounded-lg bg-zinc-950 text-white flex items-center justify-center font-bold text-xs tracking-widest shadow">LES</div> </div> </header> <div class="p-6 md:p-8 space-y-6 flex-1"> <!-- ===================================================== --> <!-- MÓDULO 1: DIRETORIA, INDICADORES GRÁFICOS & AUDITORIA --> <!-- ===================================================== --> <section id="tab-diretoria" class="tab-content space-y-6"> <div class="grid grid-cols-1 md:grid-cols-6 gap-4"> <div class="les-card p-4 border-l-4 border-l-zinc-950"> <p class="text-[10px] font-bold uppercase text-zinc-500">Clientes Cadastrados</p> <p id="dir-kpi-clientes" class="text-2xl font-extrabold text-zinc-950 mt-1">0</p> <p id="dir-kpi-frota-sub" class="text-[11px] text-zinc-400">Base Comercial & Frota</p> </div> <div class="les-card p-4 border-l-4 border-l-emerald-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Taxa de Aprovação</p> <p id="dir-kpi-taxa-aprov" class="text-2xl font-extrabold text-emerald-700 mt-1">0%</p> <p id="dir-kpi-sub-aprov" class="text-[11px] text-zinc-400">0 propostas aprovadas</p> </div> <div class="les-card p-4 border-l-4 border-l-rose-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Taxa de Recusa / Cancel.</p> <p id="dir-kpi-taxa-recusa" class="text-2xl font-extrabold text-rose-600 mt-1">0%</p> <p id="dir-kpi-sub-recusa" class="text-[11px] text-zinc-400">0 recusados ou cancelados</p> </div> <div class="les-card p-4 border-l-4 border-l-indigo-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">POs Ativos / Desembaraçados</p> <p id="dir-kpi-pos" class="text-2xl font-extrabold text-indigo-700 mt-1">0 / 0</p> <p class="text-[11px] text-zinc-400">Pedidos em andamento</p> </div> <div class="les-card p-4 border-l-4 border-l-amber-500"> <p class="text-[10px] font-bold uppercase text-zinc-500">Valor Contábil Estoque</p> <p id="dir-kpi-estoque" class="text-xl font-extrabold text-amber-700 mt-1">R$ 0,00</p> <p class="text-[11px] text-zinc-400">Ativo Circulante (1.1.03)</p> </div> <div class="les-card p-4 border-l-4 border-l-sky-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Saldo de Caixa (Diretoria)</p> <p id="dir-kpi-caixa" class="text-xl font-extrabold text-sky-700 mt-1">—</p> <p class="text-[11px] text-zinc-400">Realizado conciliado</p> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="les-card p-5 space-y-3"> <div class="flex justify-between items-center border-b pb-2"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-600 font-bold block">Indicador de Conversão Comercial</span> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Distribuição de Pedidos: Aprovados vs. Pendentes vs. Recusados/Cancelados</h3> </div> </div> <div class="h-64 flex items-center justify-center"> <canvas id="chart-diretoria-status"></canvas> </div> </div> <div class="les-card p-5 space-y-3"> <div class="flex justify-between items-center border-b pb-2"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Impacto Financeiro de Pedidos</span> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Volume Financeiro (R$) por Status de Pedido & P.O.</h3> </div> </div> <div class="h-64 flex items-center justify-center"> <canvas id="chart-diretoria-valores"></canvas> </div> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="les-card p-5 space-y-3"> <div class="flex justify-between items-center border-b pb-2"> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Rastreabilidade End-to-End (Proposta → Venda → PO → Tracking)</h3> </div> <div class="overflow-x-auto max-h-64"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-50 text-zinc-500 uppercase"> <tr><th class="p-2">Cotação Nº</th><th class="p-2">Cliente / Vendedor</th><th class="p-2">Valor</th><th class="p-2">Status / PO / Tracking</th></tr> </thead> <tbody id="dir-rastreabilidade-tbody" class="divide-y"></tbody> </table> </div> </div> <div class="les-card p-5 space-y-3"> <div class="flex justify-between items-center border-b pb-2"> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Homologação de Manuais Técnicos (Diretoria)</h3> <button onclick="switchTab('posvenda'); switchSubTab('pos', 'manuais')" class="text-xs text-sky-700 font-semibold hover:underline">Abrir Repositório</button> </div> <div class="overflow-x-auto max-h-64"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-50 text-zinc-500 uppercase"> <tr><th class="p-2">PN / Manual</th><th class="p-2">Versão</th><th class="p-2">Status / Acesso</th><th class="p-2 text-right">Ação Diretoria</th></tr> </thead> <tbody id="dir-manuais-tbody" class="divide-y"></tbody> </table> </div> </div> </div> <div class="les-card p-5 space-y-3"> <div class="flex flex-wrap justify-between items-center border-b pb-2 gap-2"> <div> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Trilha de Rastreabilidade e Auditoria Nominal Completa</h3> <p class="text-[11px] text-zinc-500">Todas as criações, edições, aprovações, movimentações de estoque e rastreamentos ficam gravados com nome, área e data/hora</p> </div> <input type="text" id="filtro-auditoria" oninput="renderAuditoria()" placeholder="Filtrar por nome ou ação (Ex: Matheus, Estoque, Desembaraçado...)" class="border rounded-lg px-3 py-1.5 text-xs w-80"> </div> <div class="overflow-x-auto max-h-80"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase sticky top-0"> <tr><th class="p-2.5">Data/Hora</th><th class="p-2.5">Colaborador (Área)</th><th class="p-2.5">Módulo</th><th class="p-2.5">Ação Executada</th><th class="p-2.5">Ref. ID</th></tr> </thead> <tbody id="dir-auditoria-tbody" class="divide-y"></tbody> </table> </div> </div> </section> <!-- ===================================================== --> <!-- MÓDULO 2: FINANCEIRO CORPORATIVO & HOLDING (DIRETORIA)--> <!-- ===================================================== --> <section id="tab-financeiro" class="tab-content space-y-6"> <div class="bg-zinc-950 text-white p-5 rounded-xl flex flex-wrap justify-between items-center gap-4 no-print shadow-md border border-zinc-800"> <div> <span id="fin-header-kicker" class="text-[10px] uppercase tracking-widest text-emerald-400 font-bold block"><i class="fa-solid fa-building-columns mr-1"></i> Visão Executiva dos Diretores • Holding LES Aviation & Subsidiárias Independentes</span> <h2 id="fin-header-subtitle" class="text-sm font-bold mt-0.5">Balanço Consolidado da Holding • Balanço LES Aviation (Drones & Paraquedas) • Balanço Subsidiária LES Defense</h2> </div> <div class="flex flex-wrap items-center gap-2"> <div id="fin-subtabs-bar" class="subtab-pill-bar-dark"> <button onclick="switchSubTab('fin', 'dash')" id="subbtn-fin-dash" class="subtab-btn active-sub"> <i class="fa-solid fa-earth-americas"></i> 1. Visão Macro (Holding LES) </button> <button onclick="switchSubTab('fin', 'dre')" id="subbtn-fin-dre" class="subtab-btn"> <i class="fa-solid fa-parachute-box"></i> 2. Fin. LES Aviation (Drones/Paraq.) <span id="badge-fin-lanc" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('fin', 'defense_fin')" id="subbtn-fin-defense_fin" class="subtab-btn"> <i class="fa-solid fa-shield-halved"></i> 3. Fin. LES Defense <span id="badge-fin-def-sub" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('fin', 'plano')" id="subbtn-fin-plano" class="subtab-btn"> <i class="fa-solid fa-list-ol"></i> 4. Plano de Contas & Metas </button> </div> <button type="button" id="btn-pdf-holding-fin" onclick="gerarPDFRelatorioHoldingConsolidado()" class="bg-emerald-500 hover:bg-emerald-400 text-zinc-950 px-3.5 py-2 rounded-xl text-xs font-extrabold shadow flex items-center gap-1.5 transition"> <i class="fa-solid fa-file-pdf"></i> <span id="lbl-btn-pdf-fin">PDF Balanço & DRE</span> </button> </div> </div> <!-- SUB-ABA 1: VISÃO MACRO DA HOLDING LES AVIATION (CONSOLIDADO DAS 2 OPERAÇÕES) --> <div id="subtab-fin-dash" class="subtab-fin subtab-anim space-y-6"> <div class="les-card p-5 bg-gradient-to-r from-zinc-950 via-zinc-900 to-zinc-950 text-white border border-zinc-800 flex flex-wrap justify-between items-center gap-4"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-400 font-extrabold block">Consolidação Executiva • Grupo / Holding LES Aviation</span> <h3 class="text-base font-extrabold mt-0.5">Visão Macro Financeira e Patrimonial de Todo o Negócio</h3> <p class="text-xs text-zinc-400 mt-0.5">Soma consolidada das duas operações independentes: <b>LES Aviation (Drones, Paraquedas & MRO)</b> + <b>Subsidiária LES Defense (Anti-Drone)</b>.</p> </div> <div class="flex flex-wrap gap-2"> <button type="button" onclick="switchSubTab('fin', 'dre')" class="px-3 py-1.5 rounded-lg bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-xs font-semibold"> Abrir Fin. LES Aviation (Drones) → </button> <button type="button" onclick="switchSubTab('fin', 'defense_fin')" class="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold"> Abrir Fin. LES Defense → </button> </div> </div> <!-- KPIs Macro da Holding --> <div class="grid grid-cols-1 md:grid-cols-5 gap-4" id="fin-kpis-grid"></div> <!-- Indicadores Consolidados de Liquidez e Giro da Holding --> <div class="les-card p-5 space-y-3"> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Indicadores Consolidados da Holding (Liquidez, Capital de Giro, Estoques Somados & EBITDA Global)</h3> <div id="fin-projecao-box" class="grid grid-cols-1 md:grid-cols-4 gap-4 pt-2"></div> </div> <!-- Tabela Comparativa Lado a Lado: LES Aviation vs. LES Defense vs. Holding --> <div class="les-card p-5 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Comparativo por Unidade de Negócio</span> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Desempenho Financeiro & Balanço por Subsidiária vs. Holding LES Aviation</h3> </div> <span class="text-[11px] text-zinc-500 italic">Visão consolidada do grupo</span> </div> <div class="overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Indicador Contábil / Patrimonial</th> <th class="p-3 text-right">LES Aviation (Drones & Paraquedas)</th> <th class="p-3 text-right text-emerald-400">Subsidiária LES Defense (Anti-Drone)</th> <th class="p-3 text-right bg-zinc-800">CONSOLIDADO HOLDING LES</th> </tr> </thead> <tbody id="tabela-fin-comparativo-holding" class="divide-y"></tbody> </table> </div> </div> <!-- DRE, DFC e Balanço Patrimonial CONSOLIDADOS DA HOLDING --> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="les-card p-5 space-y-3 border-t-4 border-t-zinc-950"> <h3 class="text-xs font-bold uppercase border-b pb-2">DRE Consolidada — Holding LES</h3> <div id="fin-holding-dre-container" class="space-y-2 text-xs"></div> </div> <div class="les-card p-5 space-y-3 border-t-4 border-t-sky-600"> <h3 class="text-xs font-bold uppercase border-b pb-2">Fluxo de Caixa Consolidado — Holding</h3> <div id="fin-holding-dfc-container" class="space-y-2 text-xs"></div> </div> <div class="les-card p-5 space-y-3 border-t-4 border-t-emerald-600"> <h3 class="text-xs font-bold uppercase border-b pb-2">Balanço Patrimonial Macro — Holding</h3> <div id="fin-holding-bp-container" class="space-y-2 text-xs"></div> </div> </div> </div> <!-- SUB-ABA 2: SUBSIDIÁRIA LES AVIATION (DRONES, PARAQUEDAS & MRO — BALANÇO + LANÇAMENTOS UNIFICADOS) --> <div id="subtab-fin-dre" class="subtab-fin subtab-anim hidden space-y-6"> <div class="les-card p-5 bg-zinc-50 border border-zinc-200 flex flex-wrap justify-between items-center gap-3"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-700 font-extrabold block">Gestão Contábil & Financeira • LES Drones</span> <h3 class="text-sm font-extrabold text-zinc-950">Balanço Patrimonial, DRE, Fluxo de Caixa e Lançamentos — LES Drones</h3> <p class="text-xs text-zinc-500">Demonstrativo de resultados, fluxo de caixa operacional, lançamentos e posição contábil de estoque (Conta 1.1.03).</p> </div> <div class="flex items-center gap-2 text-xs"> <label class="font-semibold text-zinc-700">Fechamento Contábil:</label> <input type="month" id="fin-periodo-fechado" onchange="salvarFechamentoPeriodo()" class="border rounded-lg px-2 py-1 text-xs bg-white"> </div> </div> <div class="grid grid-cols-1 md:grid-cols-5 gap-4" id="fin-aviation-kpis-grid"></div> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="les-card p-5 space-y-3 border-t-4 border-t-sky-600"> <h3 class="text-xs font-bold uppercase border-b pb-2">DRE Gerencial — LES Aviation</h3> <div id="fin-dre-container" class="space-y-2 text-xs"></div> </div> <div class="les-card p-5 space-y-3 border-t-4 border-t-indigo-600"> <h3 class="text-xs font-bold uppercase border-b pb-2">Fluxo de Caixa — LES Aviation</h3> <div id="fin-dfc-container" class="space-y-2 text-xs"></div> </div> <div class="les-card p-5 space-y-3 border-t-4 border-t-amber-500"> <h3 class="text-xs font-bold uppercase border-b pb-2">Balanço Patrimonial — LES Aviation (c/ Estoque 1.1.03)</h3> <div id="fin-bp-container" class="space-y-2 text-xs"></div> </div> </div> <div class="les-card p-6 space-y-4"> <div class="border-b pb-2"> <span class="text-[10px] uppercase tracking-widest text-sky-700 font-bold block">Livro Razão • Operação LES Aviation (Drones, Paraquedas & MRO)</span> <h3 class="text-sm font-bold uppercase text-zinc-900">Novo Lançamento Financeiro — LES Aviation</h3> </div> <form onsubmit="salvarLancamentoFinanceiro(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <div> <label class="font-semibold block mb-1">Natureza / Tipo *</label> <select id="fin-natureza" required class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="RECEITA">Receita Operacional</option> <option value="CPV_CMV">Custo de Produto/Serviço Vendido (CPV/CMV)</option> <option value="DESPESA_OP">Despesa Operacional</option> <option value="COMPRA_ESTOQUE">Entrada / Compra de Estoque (Ativo Circulante)</option> <option value="BAIXA_ESTOQUE">Baixa / Ajuste de Estoque (Contábil)</option> <option value="AQUISICAO_ATIVO">Aquisição de Ativo / Imobilizado (CAPEX)</option> <option value="TRIBUTO_LUCRO">Tributos sobre o Lucro</option> <option value="APORTE_SOCIO">Aporte de Sócios (Patrimônio Líquido)</option> <option value="EMPRESTIMO">Empréstimo / Financiamento (Passivo)</option> <option value="AMORTIZACAO">Amortização de Dívida</option> <option value="TRANSFERENCIA">Transferência entre Contas Próprias</option> </select> </div> <div> <label class="font-semibold block mb-1">Categoria Específica *</label> <select id="fin-categoria" required class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="Venda de Equipamentos / Sistemas">Venda de Equipamentos / Sistemas</option> <option value="Serviços Técnicos / MRO / Integração">Serviços Técnicos / MRO / Integração</option> <option value="Importação / Compras Internacionais">Importação / Compras Internacionais</option> <option value="Estoque & Almoxarifado (Ativo 1.1.03)">Estoque & Almoxarifado (Ativo 1.1.03)</option> <option value="Fretes Internacionais e Seguros">Fretes Internacionais e Seguros</option> <option value="Tributos, Desembaraço e Taxas">Tributos, Desembaraço e Taxas</option> <option value="Comissões Comerciais (4%)">Comissões Comerciais (4%)</option> <option value="Peças de Reposição e Insumos">Peças de Reposição e Insumos</option> <option value="Administrativo / Corporativo">Administrativo / Corporativo</option> </select> </div> <div><label class="font-semibold block mb-1">Descrição *</label><input type="text" id="fin-desc" required class="w-full border rounded-lg px-2.5 py-2"></div> <div> <label class="font-semibold block mb-1">Centro de Custo *</label> <select id="fin-cc" class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="Diretoria / Corporativo">Diretoria / Corporativo</option> <option value="Comercial / Vendas">Comercial / Vendas</option> <option value="Logística & Importação">Logística & Importação</option> <option value="Estoque & Almoxarifado">Estoque & Almoxarifado</option> <option value="Pós-venda / Engenharia MRO">Pós-venda / Engenharia MRO</option> </select> </div> <div><label class="font-semibold block mb-1">Cliente / Fornecedor</label><input type="text" id="fin-entidade" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Doc. de Origem (Proposta / PO / Estoque)</label><input type="text" id="fin-doc" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Data de Competência *</label><input type="date" id="fin-competencia" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Data de Vencimento *</label><input type="date" id="fin-vencimento" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Valor Bruto (R$) *</label><input type="number" step="0.01" id="fin-bruto" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Impostos / Retenções (R$)</label><input type="number" step="0.01" id="fin-impostos" value="0" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Descontos / Acréscimos (R$)</label><input type="number" step="0.01" id="fin-ajuste" value="0" class="w-full border rounded-lg px-2.5 py-2"></div> <div> <label class="font-semibold block mb-1">Status de Liquidação *</label> <select id="fin-status" class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="PREVISTO">Em Aberto (A Pagar / A Receber)</option> <option value="REALIZADO">Liquidado / Efetivado em Caixa</option> <option value="CONTABIL_ESTOQUE">Reflexo Contábil de Estoque (Ativo)</option> <option value="PARCIAL">Liquidado Parcialmente</option> <option value="ATRASADO">Vencido / Inadimplente</option> </select> </div> <div><label class="font-semibold block mb-1">Data Pagto / Recebimento</label><input type="date" id="fin-liquidacao" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Conta Bancária / Contábil *</label><input type="text" id="fin-conta" required value="Banco Principal LES Aviation" class="w-full border rounded-lg px-2.5 py-2"></div> <div class="md:col-span-2 flex items-end justify-end"> <button type="submit" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-plus mr-1 text-emerald-400"></i> Registrar Lançamento (LES Aviation) </button> </div> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr><th class="p-3">ID / Origem</th><th class="p-3">Competência / Venc.</th><th class="p-3">Natureza / Categoria</th><th class="p-3">Descrição / Rastreabilidade</th><th class="p-3">Valor Líquido</th><th class="p-3">Status</th><th class="p-3 text-right">Ações</th></tr> </thead> <tbody id="tabela-fin-lancamentos" class="divide-y"></tbody> </table> </div> </div> <!-- SUB-ABA 3: SUBSIDIÁRIA LES DEFENSE (BALANÇO + LANÇAMENTOS UNIFICADOS) --> <div id="subtab-fin-defense_fin" class="subtab-fin subtab-anim hidden space-y-6"> <div class="les-card p-5 bg-emerald-950 text-white border border-emerald-800 flex flex-wrap justify-between items-center gap-3"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-400 font-extrabold block"><i class="fa-solid fa-shield-halved mr-1"></i> Gestão Contábil & Financeira • LES Defense</span> <h3 class="text-sm font-extrabold">Balanço Patrimonial, DRE, Fluxo de Caixa e Lançamentos — LES Defense</h3> <p class="text-xs text-emerald-200/80">Demonstrativo de resultados, fluxo de caixa operacional, lançamentos e posição contábil de estoque (Conta 1.1.03-DEF).</p> </div> </div> <div class="grid grid-cols-1 md:grid-cols-5 gap-4" id="fin-dir-defense-kpis-grid"></div> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="les-card p-5 space-y-3 border-t-4 border-t-emerald-600"> <h3 class="text-xs font-bold uppercase border-b pb-2">DRE Gerencial — Subsidiária LES Defense</h3> <div id="fin-dir-def-dre-container" class="space-y-2 text-xs"></div> </div> <div class="les-card p-5 space-y-3 border-t-4 border-t-teal-600"> <h3 class="text-xs font-bold uppercase border-b pb-2">Fluxo de Caixa — Subsidiária LES Defense</h3> <div id="fin-dir-def-dfc-container" class="space-y-2 text-xs"></div> </div> <div class="les-card p-5 space-y-3 border-t-4 border-t-amber-500"> <h3 class="text-xs font-bold uppercase border-b pb-2">Balanço Patrimonial — Subsidiária LES Defense</h3> <div id="fin-dir-def-bp-container" class="space-y-2 text-xs"></div> </div> </div> <!-- Formulário Único de Novo Lançamento Financeiro LES Defense --> <div class="les-card p-6 space-y-4"> <div class="border-b pb-2"> <span class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Livro Razão • LES Defense</span> <h3 class="text-sm font-bold uppercase text-zinc-900">Novo Lançamento Financeiro — Exclusivo LES Defense</h3> </div> <form onsubmit="salvarLancamentoFinanceiroDefense(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <div> <label class="font-semibold block mb-1">Natureza Contábil *</label> <select id="def-fin-natureza" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="RECEITA">Receita Operacional Defense</option> <option value="CPV_CMV">Custo de Serviço / Operação de Campo (CPV/CSP)</option> <option value="DESPESA_OP">Despesa Operacional / P&D / Comercial Defense</option> <option value="COMPRA_ESTOQUE">Aquisição de Sensores / Estoque Defense (Ativo)</option> <option value="TRIBUTO_LUCRO">Impostos e Tributos Defense</option> <option value="APORTE_SOCIO">Aporte da Holding / Capital Social Defense</option> </select> </div> <div> <label class="font-semibold block mb-1">Categoria LES Defense *</label> <select id="def-fin-categoria" required class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="Consultoria Técnica & Site Survey">Consultoria Técnica & Site Survey</option> <option value="Contrato de Monitoramento 24/7 (Recorrente)">Contrato de Monitoramento 24/7 (Recorrente)</option> <option value="Monitoramento de Eventos / Operação Dedicada">Monitoramento de Eventos / Operação Dedicada</option> <option value="Sensores Passivos RF / Remote ID / Hardware">Sensores Passivos RF / Remote ID / Hardware</option> <option value="Comissões Comerciais Defense (4%)">Comissões Comerciais Defense (4%)</option> <option value="Logística de Campo, Diárias & Viagens Técnicas">Logística de Campo, Diárias & Viagens Técnicas</option> <option value="Software, Licenças & Conectividade Starlink/4G">Software, Licenças & Conectividade Starlink/4G</option> <option value="Engenharia, P&D & Suportes Táticos">Engenharia, P&D & Suportes Táticos</option> </select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Descrição do Lançamento *</label> <input type="text" id="def-fin-desc" required placeholder="Ex: Faturamento Site Survey / Compra de Sensor RF Passivo..." class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Cliente / Fornecedor Defense *</label> <input type="text" id="def-fin-entidade" required placeholder="Nome da empresa ou fornecedor" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Doc. / Contrato Ref.</label> <input type="text" id="def-fin-doc" placeholder="Ex: DEF-2026-001" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Data Competência *</label> <input type="date" id="def-fin-competencia" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Data Vencimento *</label> <input type="date" id="def-fin-vencimento" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Valor Bruto (R$) *</label> <input type="number" step="0.01" id="def-fin-bruto" required placeholder="0.00" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div> <label class="font-semibold block mb-1">Impostos / Retenções (R$)</label> <input type="number" step="0.01" id="def-fin-impostos" value="0" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Status de Liquidação *</label> <select id="def-fin-status" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="PREVISTO">Em Aberto (A Receber / A Pagar)</option> <option value="REALIZADO">Liquidado / Efetivado em Caixa Defense</option> <option value="CONTABIL_ESTOQUE">Reflexo Contábil de Estoque Defense</option> </select> </div> <div> <label class="font-semibold block mb-1">Conta Bancária / Contábil *</label> <input type="text" id="def-fin-conta" required value="Banco Exclusivo LES Defense" class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="md:col-span-4 flex justify-end pt-1"> <button type="submit" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-sack-dollar mr-1.5 text-emerald-400"></i> Registrar no Financeiro LES Defense </button> </div> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">ID / Doc</th> <th class="p-3">Competência / Venc.</th> <th class="p-3">Natureza / Categoria</th> <th class="p-3">Descrição / Cliente-Fornecedor</th> <th class="p-3">Valor Líquido</th> <th class="p-3">Status</th> <th class="p-3 text-right">Ações</th> </tr> </thead> <tbody id="tabela-dir-def-lancamentos" class="divide-y"></tbody> </table> </div> </div> <!-- SUB-ABA 4: PLANO DE CONTAS & ORÇAMENTO --> <div id="subtab-fin-plano" class="subtab-fin subtab-anim hidden space-y-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="les-card p-5 space-y-3"> <h3 id="fin-plano-contas-titulo" class="text-xs font-bold uppercase border-b pb-2">Plano de Contas Contábil</h3> <form onsubmit="salvarContaContabil(event)" class="grid grid-cols-3 gap-2 text-xs"> <input type="text" id="pc-codigo" required placeholder="Cód (1.1.03)" class="border rounded-lg px-2 py-1.5"> <input type="text" id="pc-nome" required placeholder="Nome da Conta" class="border rounded-lg px-2 py-1.5"> <select id="pc-grupo" class="border rounded-lg px-2 py-1.5"><option value="ATIVO">Ativo</option><option value="PASSIVO">Passivo</option><option value="PL">PL</option><option value="RECEITA">Receita</option><option value="CUSTO">Custo</option><option value="DESPESA">Despesa</option></select> <button type="submit" class="col-span-3 bg-zinc-950 text-white py-1.5 rounded-lg font-semibold">Adicionar Conta</button> </form> <div class="overflow-y-auto max-h-60"><table class="w-full text-left text-xs"><tbody id="tabela-plano-contas" class="divide-y"></tbody></table></div> </div> <div class="les-card p-5 space-y-3"> <h3 class="text-xs font-bold uppercase border-b pb-2">Orçamento & Cenários Corporativos</h3> <div class="grid grid-cols-2 gap-3 text-xs"> <div><label class="font-semibold block mb-1">Meta de Receita (R$)</label><input type="number" id="orc-meta-receita" onchange="salvarOrcamento()" class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Limite de Despesas (R$)</label><input type="number" id="orc-limite-despesa" onchange="salvarOrcamento()" class="w-full border rounded-lg px-2 py-1.5"></div> </div> </div> </div> </div> </section> <!-- ===================================================== --> <!-- MÓDULO 3: VENDAS / CRM COMERCIAL --> <!-- ===================================================== --> <section id="tab-vendas" class="tab-content space-y-6"> <div class="bg-white border border-zinc-200 p-5 rounded-xl flex flex-wrap justify-between items-center gap-4 no-print shadow-sm"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Módulo Comercial • Responsável por Cotação do Fabricante & Comprovante de Pagamento</span> <h2 class="text-sm font-bold text-zinc-900 mt-0.5">Propostas Executivas PDF • Dossiê Comercial • Clientes • Anamnese • Pipeline</h2> </div> <div class="subtab-pill-bar"> <button onclick="switchSubTab('ven', 'propostas')" id="subbtn-ven-propostas" class="subtab-btn active-sub"> <i class="fa-solid fa-file-signature"></i> 1. Cotações & Pedidos <span id="badge-ven-prop" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('ven', 'clientes')" id="subbtn-ven-clientes" class="subtab-btn"> <i class="fa-solid fa-building-user"></i> 2. Clientes <span id="badge-ven-cli" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('ven', 'anamnese')" id="subbtn-ven-anamnese" class="subtab-btn"> <i class="fa-solid fa-clipboard-check"></i> 3. Anamnese (A–H) </button> <button onclick="switchSubTab('ven', 'dash')" id="subbtn-ven-dash" class="subtab-btn"> <i class="fa-solid fa-filter-circle-dollar"></i> 4. Pipeline </button> <button onclick="switchSubTab('ven', 'registro')" id="subbtn-ven-registro" class="subtab-btn"> <i class="fa-solid fa-handshake"></i> 5. Vendas & Dossiê <span id="badge-ven-vendas" class="subtab-badge">0</span> </button> </div> </div> <!-- RESUMO PESSOAL DO RESPONSÁVEL PELA VENDA (SEM LABELS DE COMISSÃO, VISÍVEL APENAS AO PRÓPRIO RESPONSÁVEL) --> <div id="ven-comissoes-kpis-banner" class="hidden grid grid-cols-1 md:grid-cols-4 gap-4 no-print"></div> <!-- Sub-aba 3.1: Gerador e Editor de Proposta Comercial Executiva --> <div id="subtab-ven-propostas" class="subtab-ven subtab-anim space-y-6"> <div class="les-card p-6 space-y-5" id="card-form-proposta"> <div class="border-b pb-3 flex flex-wrap justify-between items-center gap-2"> <div> <span id="prop-form-mode-badge" class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Nova Cotação • Catálogo de P/N Integrado</span> <h3 id="prop-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Emitir / Editar Cotação Comercial Produto Aeronáutico</h3> </div> <div class="flex items-center gap-2"> <button type="button" id="btn-cancelar-edicao-prop" onclick="cancelarModoEdicaoProposta()" class="hidden px-3 py-1 bg-zinc-200 hover:bg-zinc-300 text-zinc-800 rounded-lg text-xs font-bold"> Cancelar Edição </button> <span class="px-3 py-1 bg-emerald-50 text-emerald-900 border border-emerald-200 rounded-lg text-xs font-bold">Vendas: Anexa Cotação Fab. & Comprovante</span> </div> </div> <form id="form-proposta" onsubmit="salvarProposta(event)" class="space-y-4 text-xs"> <input type="hidden" id="prop-edit-original-num" value=""> <div class="grid grid-cols-1 md:grid-cols-4 gap-3"> <div> <label class="font-semibold block mb-1">Cotação Nº *</label> <input type="text" id="prop-numero" required placeholder="Ex: 0104" class="w-full border rounded-lg px-2.5 py-2 font-mono font-bold"> </div> <div> <label class="font-semibold block mb-1">Data de Emissão *</label> <input type="date" id="prop-emissao" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Validade da Proposta *</label> <input type="text" id="prop-validade" required value="15 DIAS" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Fabricante / Fornecedor Ref. *</label> <input type="text" id="prop-fabricante" required value="Drone Rescue Systems GmbH" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Cliente / Empresa Cadastrada *</label> <select id="prop-cliente" required class="w-full border rounded-lg px-2.5 py-2 bg-white"></select> </div> <div> <label class="font-semibold block mb-1 text-emerald-800">Responsável pela Venda *</label> <select id="prop-vendedor" required onchange="recalcularPreviewProposta()" class="w-full border-2 border-emerald-500 rounded-lg px-2.5 py-2 bg-emerald-50/40 font-bold text-zinc-900"> <option value="Lucas Azevedo">Lucas Azevedo</option> <option value="Caio Cesar">Caio Cesar</option> <option value="Matheus Azevedo">Matheus Azevedo</option> <option value="Gabriel Bezerra">Gabriel Bezerra</option> <option value="Ramon Dias">Ramon Dias</option> </select> </div> <div> <label class="font-semibold block mb-1">Signatário do Cliente (Nome/Cargo)</label> <input type="text" id="prop-signatario-cli" placeholder="Para o Termo de Aceite" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Contato Comercial (Tel / E-mail)</label> <input type="text" id="prop-consultor-contato" placeholder="Ex: (62) 99999-0000 | comercial@lesaviation.com" class="w-full border rounded-lg px-2.5 py-2"> </div> </div> <!-- Dados iniciais da Cotação do Fabricante & Comprovante (Preenchimento pelo Perfil de Vendas) --> <div class="bg-emerald-50/60 p-4 rounded-xl border border-emerald-200 grid grid-cols-1 md:grid-cols-3 gap-3"> <div class="md:col-span-3 flex items-center justify-between"> <span class="text-[11px] font-extrabold uppercase text-emerald-900"><i class="fa-solid fa-file-shield mr-1"></i> Informações da Cotação do Fabricante & Pagamento (Exclusivo Perfil de Vendas)</span> <span class="text-[10px] text-emerald-700 font-medium">Você também pode anexar os PDFs/Comprovantes na tabela abaixo</span> </div> <div> <label class="font-semibold block mb-1 text-emerald-950">Nº / Ref. Cotação do Fabricante (DRS)</label> <input type="text" id="prop-cotacao-fab-ref" placeholder="Ex: DRS-QUO-2026-881 — EUR 4.200" class="w-full border border-emerald-300 rounded-lg px-2.5 py-1.5 bg-white"> </div> <div> <label class="font-semibold block mb-1 text-emerald-950">Dados do Comprovante de Pagto (Sinal)</label> <input type="text" id="prop-comprovante-ref" placeholder="Ex: PIX 30% confirmado / ID Transação" class="w-full border border-emerald-300 rounded-lg px-2.5 py-1.5 bg-white"> </div> <div> <label class="font-semibold block mb-1 text-emerald-950">Sinal / 1º Pagamento (% ou R$)</label> <input type="text" id="prop-sinal-regra" value="30% no ato do pedido" class="w-full border border-emerald-300 rounded-lg px-2.5 py-1.5 bg-white"> </div> </div> <!-- Tabela Dinâmica de Produtos da Cotação --> <div class="bg-zinc-50 p-4 rounded-xl border space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2"> <div> <h4 class="font-bold uppercase text-zinc-900">02. Descrição dos Produtos (Selecione o P/N da Base p/ Preencher Automaticamente)</h4> </div> <button type="button" onclick="adicionarLinhaItemProposta()" class="bg-zinc-900 text-white px-3 py-1.5 rounded-lg text-[11px] font-semibold"> <i class="fa-solid fa-plus mr-1"></i> Adicionar Linha de Produto </button> </div> <div class="grid grid-cols-12 gap-2 text-[10px] font-bold uppercase text-zinc-500 px-1"> <div class="col-span-3">P/N (Base de Dados)</div> <div class="col-span-5">Nome / Descrição do Produto</div> <div class="col-span-1">Qtd.</div> <div class="col-span-2">Valor Unit. (R$)</div> <div class="col-span-1 text-center">Remover</div> </div> <div id="prop-itens-container" class="space-y-2"></div> </div> <!-- Resumo Financeiro, Taxa de Importação em % e Modo de Pagamento Dinâmico --> <div class="bg-zinc-50 p-4 rounded-xl border space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2"> <h4 class="font-bold uppercase text-zinc-900">03 & 04. Opção de Pagamento, Taxa de Importação (%) & Resumo Financeiro</h4> <span class="text-[10px] font-bold uppercase text-emerald-700 bg-emerald-50 border border-emerald-200 px-2.5 py-0.5 rounded-full">Sincronizado automaticamente com Vendas & Contábil</span> </div> <div class="grid grid-cols-1 md:grid-cols-4 gap-3"> <div> <label class="font-semibold block mb-1">Frete (R$)</label> <input type="number" step="0.01" id="prop-frete" value="0" oninput="recalcularPreviewProposta()" class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> <div> <label class="font-semibold block mb-1">Taxa de Importação (%) *</label> <div class="relative"> <input type="number" step="0.01" min="0" id="prop-taxa-imp-perc" value="0" oninput="recalcularPreviewProposta()" placeholder="Ex: 15" class="w-full border rounded-lg px-2.5 py-2 pr-8 bg-white font-bold text-zinc-900"> <span class="absolute right-2.5 top-2 text-xs font-extrabold text-zinc-500">%</span> </div> <input type="hidden" id="prop-taxas-imp" value="0"> <span id="prop-taxa-imp-reais-preview" class="text-[10px] font-mono text-zinc-500 block mt-0.5">Equivale a: R$ 0,00</span> </div> <div> <label class="font-semibold block mb-1 text-emerald-900">Modo de Pagamento *</label> <select id="prop-forma-pagamento" onchange="aoMudarFormaPagamentoProposta()" class="w-full border-2 border-emerald-500 rounded-lg px-2.5 py-2 bg-white font-bold text-zinc-900"> <option value="PIX">PIX — Com 5% de Desconto</option> <option value="CARTAO">Cartão de Crédito — Opções de Parcelamento</option> <option value="DINHEIRO">Dinheiro — Valor à Vista</option> </select> </div> <div id="box-prop-parcelas-cartao" class="hidden"> <label class="font-semibold block mb-1 text-sky-900">Opções de Cartão (Parcelas)</label> <select id="prop-parcelas-cartao" onchange="recalcularPreviewProposta()" class="w-full border-2 border-sky-500 rounded-lg px-2.5 py-2 bg-white font-bold text-sky-950"> <option value="1">1x Sem Juros no Cartão</option> <option value="2">2x Sem Juros no Cartão</option> <option value="3">3x Sem Juros no Cartão</option> <option value="4" selected>4x Sem Juros no Cartão</option> <option value="5">5x no Cartão</option> <option value="6">6x no Cartão</option> <option value="7">7x no Cartão</option> <option value="8">8x no Cartão</option> <option value="9">9x no Cartão</option> <option value="10">10x no Cartão</option> <option value="11">11x no Cartão</option> <option value="12">12x no Cartão</option> </select> </div> </div> <!-- Painel Dinâmico: Mostra APENAS o valor do modo de pagamento selecionado --> <div class="pt-1"> <!-- MODO PIX SELECIONADO --> <div id="box-modo-pagto-pix" class="bg-emerald-50/80 p-4 rounded-xl border-2 border-emerald-400 flex flex-wrap justify-between items-center gap-4"> <div> <span class="text-[10px] font-extrabold uppercase tracking-wider text-emerald-800 block"><i class="fa-brands fa-pix mr-1"></i> Modo Selecionado: PIX com 5% de Desconto</span> <input type="text" id="prop-valor-pix-preview" readonly value="R$ 0,00" class="w-full bg-transparent font-mono font-black text-xl text-emerald-800 mt-0.5 focus:outline-none"> <span id="prop-economia-pix-preview" class="text-[11px] text-emerald-700 font-semibold block">Desconto de 5%: R$ 0,00</span> </div> <div class="text-right"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">Valor Base s/ Desconto</span> <input type="text" id="prop-valor-cheio-preview" readonly value="R$ 0,00" class="bg-transparent font-mono font-bold text-sm text-zinc-700 text-right focus:outline-none"> </div> </div> <!-- MODO CARTÃO SELECIONADO --> <div id="box-modo-pagto-cartao" class="hidden bg-sky-50/80 p-4 rounded-xl border-2 border-sky-400 space-y-2.5"> <div class="flex flex-wrap justify-between items-center gap-4"> <div> <span class="text-[10px] font-extrabold uppercase tracking-wider text-sky-900 block"><i class="fa-regular fa-credit-card mr-1"></i> Modo Selecionado: Cartão de Crédito</span> <input type="text" id="prop-valor-cartao-preview" readonly value="4x de R$ 0,00 s/ juros" class="w-full bg-transparent font-mono font-black text-xl text-sky-900 mt-0.5 focus:outline-none"> <span id="prop-sub-cartao-preview" class="text-[11px] text-sky-700 font-semibold block">Total no Cartão: R$ 0,00</span> </div> <div class="text-right"> <span class="text-[10px] font-bold uppercase text-sky-800 block">Valor Total no Cartão</span> <span id="prop-total-cartao-badge" class="font-mono font-extrabold text-base text-sky-950">R$ 0,00</span> </div> </div> <div id="grid-opcoes-parcelas-cartao-preview" class="grid grid-cols-2 md:grid-cols-4 gap-2 pt-2 border-t border-sky-200 text-[11px]"></div> </div> <!-- MODO DINHEIRO SELECIONADO --> <div id="box-modo-pagto-dinheiro" class="hidden bg-zinc-900 text-white p-4 rounded-xl border border-zinc-800 flex flex-wrap justify-between items-center gap-4"> <div> <span class="text-[10px] font-extrabold uppercase tracking-wider text-emerald-400 block"><i class="fa-solid fa-money-bill-wave mr-1"></i> Modo Selecionado: Dinheiro à Vista</span> <input type="text" id="prop-total-preview" readonly value="R$ 0,00" class="w-full bg-transparent font-mono font-black text-xl text-white mt-0.5 focus:outline-none"> <span id="prop-resumo-pagto-ativo" class="text-[11px] text-zinc-300 block">Pagamento em Dinheiro</span> </div> </div> <!-- Indicador discreto visível APENAS para o responsável pela venda (sem label escrita de comissão) --> <div id="box-retorno-responsavel-preview" class="hidden mt-2.5 px-3.5 py-2 rounded-lg bg-zinc-100 border border-zinc-300 flex justify-between items-center text-xs"> <span id="prop-sub-comissao-preview" class="font-semibold text-zinc-700">Responsável</span> <input type="text" id="prop-comissao-vendedor-preview" readonly value="R$ 0,00" class="bg-transparent font-mono font-extrabold text-zinc-900 text-right focus:outline-none w-36"> </div> </div> <div> <label class="font-semibold block mb-1">05. Observações, Exclusões ou Detalhes da Cotação</label> <input type="text" id="prop-exclusoes" value="Prazo estimado de fornecimento sujeito à disponibilidade do fabricante e desembaraço aduaneiro. ICMS 17%." class="w-full border rounded-lg px-2.5 py-2 bg-white"> </div> </div> <div class="flex justify-end gap-2"> <button type="submit" id="btn-submit-proposta" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-semibold shadow"> <i class="fa-solid fa-file-signature mr-1.5"></i> Salvar Pedido / Proposta Comercial </button> </div> </form> </div> <!-- Lista de Propostas Emitidas --> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Cotação Nº / Emissão</th> <th class="p-3">Cliente & Responsável</th> <th class="p-3">Produtos & P/N</th> <th class="p-3">Valor (Modo Selecionado)</th> <th class="p-3">Dossiê Vendas (Fab. & Pagto)</th> <th class="p-3">Status do Rastreio (Logística) & Pedido</th> <th class="p-3 text-right">Ações (PDF / Dossiê / Aprovar)</th> </tr> </thead> <tbody id="tabela-propostas" class="divide-y"></tbody> </table> </div> </div> <!-- Sub-aba 3.2: Cadastro de Clientes --> <div id="subtab-ven-clientes" class="subtab-ven subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4" id="card-form-cliente"> <div class="flex justify-between items-center border-b pb-2"> <h3 id="cli-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Cadastro / Edição de Cliente</h3> <button type="button" id="btn-cancelar-edicao-cli" onclick="cancelarEdicaoCliente()" class="hidden px-3 py-1 bg-amber-100 text-amber-900 rounded-lg text-xs font-bold">Cancelar Edição</button> </div> <form id="form-cliente" onsubmit="salvarCliente(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="cli-edit-id" value=""> <div><label class="font-semibold block mb-1">Nome do Cliente / Contato *</label><input type="text" id="cli-contato" required placeholder="Nome do contato" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Empresa / Razão Social *</label><input type="text" id="cli-nome" required placeholder="Razão Social" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Nome Fantasia</label><input type="text" id="cli-fantasia" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">CNPJ / CPF *</label><input type="text" id="cli-cnpj" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Telefone / WhatsApp *</label><input type="text" id="cli-whatsapp" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">E-mail *</label><input type="email" id="cli-email" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Cidade / UF *</label><input type="text" id="cli-cidade" required value="Anápolis - GO" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Origem</label><input type="text" id="cli-origem" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Modelo do Drone</label><input type="text" id="cli-drone" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Peso Ref. (kg)</label><input type="number" step="0.01" id="cli-peso" value="0" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Quantidade</label><input type="number" id="cli-qtd" value="1" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Forma de Pagamento</label><input type="text" id="cli-pagamento" value="Pix 30%/70% ou Cartão 4x" class="w-full border rounded-lg px-2.5 py-2"></div> <div class="md:col-span-2"><label class="font-semibold block mb-1">Endereço de Entrega Completo *</label><input type="text" id="cli-endereco" required placeholder="Rua, Nº, Bairro, Cidade - UF, CEP" class="w-full border rounded-lg px-2.5 py-2"></div> <div class="md:col-span-2"><label class="font-semibold block mb-1">Observações</label><input type="text" id="cli-obs" class="w-full border rounded-lg px-2.5 py-2"></div> <div class="md:col-span-4 flex justify-end"><button type="submit" id="btn-submit-cli" class="bg-zinc-950 text-white px-6 py-2 rounded-lg font-semibold">Salvar Cliente</button></div> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr><th class="p-3">Empresa / Cliente</th><th class="p-3">CNPJ</th><th class="p-3">Contato / E-mail / Tel</th><th class="p-3">Drone / Qtd</th><th class="p-3">Endereço de Entrega / Rastreabilidade</th><th class="p-3 text-right">Ações</th></tr> </thead> <tbody id="tabela-clientes" class="divide-y"></tbody> </table> </div> </div> <!-- Sub-aba 3.3: Anamnese --> <div id="subtab-ven-anamnese" class="subtab-ven subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4"> <h3 class="text-sm font-bold uppercase text-zinc-900">Ficha de Qualificação Técnica e Comercial (Anamnese A–H)</h3> <form onsubmit="salvarAnamnese(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <div><label class="font-semibold block mb-1">Cliente *</label><select id="anam-cliente" required class="w-full border rounded-lg px-2 py-1.5"></select></div> <div><label class="font-semibold block mb-1">Demanda *</label><select id="anam-demanda" class="w-full border rounded-lg px-2 py-1.5"><option value="PARAQUEDAS">Sistema de Recuperação</option><option value="MANUTENCAO_PECAS">Manutenção / Peças</option><option value="DRONE_INTEGRACAO">Drone / Integração</option></select></div> <div><label class="font-semibold block mb-1">Aplicação / Ambiente *</label><input type="text" id="anam-aplicacao" required class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Urgência *</label><input type="text" id="anam-urgencia" required class="w-full border rounded-lg px-2 py-1.5"></div> <div class="md:col-span-4"><label class="font-semibold block mb-1">Necessidade nas palavras do cliente *</label><input type="text" id="anam-palavras-cliente" required class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Modelo do Drone *</label><input type="text" id="anam-drone-mod" required class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Peso Equipamento (kg)</label><input type="number" step="0.01" id="anam-peso-eq" class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Peso Carga Útil (kg)</label><input type="number" step="0.01" id="anam-peso-payload" class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">MTOW (kg)</label><input type="number" step="0.01" id="anam-peso-mtow" class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Peso Operacional Real (kg)</label><input type="number" step="0.01" id="anam-peso-op" class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Pontos Fixação (Paraquedas)</label><input type="text" id="anam-pq-fixacao" class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Orçamento / Prazo *</label><input type="text" id="anam-orcamento" required class="w-full border rounded-lg px-2 py-1.5"></div> <div><label class="font-semibold block mb-1">Próxima Ação *</label><input type="text" id="anam-prox-acao" required class="w-full border rounded-lg px-2 py-1.5"></div> <div class="md:col-span-4 flex justify-end"><button type="submit" class="bg-zinc-950 text-white px-6 py-2 rounded-lg font-semibold">Salvar Anamnese</button></div> </form> </div> <div class="les-card p-5 space-y-3"><div id="lista-anamneses-container" class="space-y-3"></div></div> </div> <!-- Sub-aba 3.4: Pipeline --> <div id="subtab-ven-dash" class="subtab-ven subtab-anim hidden space-y-6"> <div class="les-card p-5 space-y-4"> <h3 class="text-xs font-bold uppercase tracking-wider text-zinc-900">Nova Oportunidade / Lead no Fluxo Comercial</h3> <form onsubmit="salvarOportunidade(event)" class="grid grid-cols-1 md:grid-cols-5 gap-3 text-xs"> <select id="op-cliente" required class="border rounded-lg px-2.5 py-2"></select> <input type="text" id="op-titulo" required placeholder="Demanda (Ex: 2x Paraquedas M350)" class="border rounded-lg px-2.5 py-2"> <select id="op-categoria" class="border rounded-lg px-2.5 py-2"> <option value="Sistema de Recuperação">Sistema de Recuperação por Paraquedas</option> <option value="Drone / RPAS">Aeronave Remotamente Pilotada (Drone)</option> <option value="Manutenção / MRO">Manutenção / MRO</option> <option value="Peças de Reposição">Peças de Reposição</option> </select> <select id="op-etapa" class="border rounded-lg px-2.5 py-2"> <option value="1. Demanda espontânea / Prospecção">1. Demanda espontânea / Prospecção</option> <option value="4. Anamnese técnica e comercial">4. Anamnese técnica e comercial</option> <option value="7. Pedido de cotação">7. Pedido de cotação</option> <option value="9. Elaboração da proposta">9. Elaboração da proposta</option> <option value="10. Envio da proposta ao cliente">10. Envio da proposta ao cliente</option> <option value="13. Registro da venda">13. Registro da venda</option> </select> <input type="number" step="0.01" id="op-valor" placeholder="Valor Estimado (R$)" class="border rounded-lg px-2.5 py-2"> <input type="date" id="op-retorno" class="border rounded-lg px-2.5 py-2"> <input type="text" id="op-origem" placeholder="Origem do Lead" class="border rounded-lg px-2.5 py-2 md:col-span-2"> <button type="submit" class="md:col-span-2 bg-zinc-950 text-white rounded-lg font-semibold py-2">Registrar Oportunidade</button> </form> <div class="overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr><th class="p-2.5">ID</th><th class="p-2.5">Cliente</th><th class="p-2.5">Oportunidade</th><th class="p-2.5">Etapa</th><th class="p-2.5">Valor Estimado</th><th class="p-2.5">Rastreabilidade</th></tr> </thead> <tbody id="tabela-oportunidades" class="divide-y"></tbody> </table> </div> </div> </div> <!-- Sub-aba 3.5: Vendas Aprovadas --> <div id="subtab-ven-registro" class="subtab-ven subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-3"> <div> <h3 class="text-sm font-bold uppercase text-zinc-900">Registro de Vendas Aprovadas & Dossiê Comercial</h3> <p class="text-xs text-zinc-500">Acompanhe os pedidos aprovados e anexe a Cotação do Fabricante e o Comprovante de Pagamento.</p> </div> </div> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"><tr><th class="p-3">ID Venda / Cotação</th><th class="p-3">Cliente & Responsável</th><th class="p-3">Itens</th><th class="p-3">Valor Total</th><th class="p-3">Status & Rastreamento</th><th class="p-3 text-right">Dossiê (Vendas)</th></tr></thead> <tbody id="tabela-vendas" class="divide-y"></tbody> </table> </div> </div> </section> <!-- ===================================================== --> <!-- MÓDULO EXCLUSIVO: SUBSIDIÁRIA LES DEFENSE --> <!-- ===================================================== --> <section id="tab-defense" class="tab-content space-y-6"> <div class="bg-zinc-950 text-white p-5 rounded-xl flex flex-wrap justify-between items-center gap-4 no-print shadow-md border border-zinc-800"> <div> <span id="def-header-kicker" class="text-[10px] uppercase tracking-widest text-emerald-400 font-bold block"><i class="fa-solid fa-shield-halved mr-1"></i> Subsidiária Independente • LES Defense • Operação, Clientes & Estoque Exclusivos</span> <h2 id="def-header-subtitle" class="text-sm font-bold mt-0.5">Consultoria (Site Survey) • Projetos & Contratos B2B • Base Própria de Clientes Defense • Estoque Exclusivo Defense</h2> </div> <div id="def-subtabs-bar" class="subtab-pill-bar-dark"> <button onclick="abrirAbaDefenseEspecifica('consultoria')" id="subbtn-def-consultoria" class="subtab-btn active-sub"> <i class="fa-solid fa-satellite-dish"></i> 1. Consultoria & Site Survey <span id="badge-def-cons" class="subtab-badge">0</span> </button> <button onclick="abrirAbaDefenseEspecifica('contratos')" id="subbtn-def-contratos" class="subtab-btn"> <i class="fa-solid fa-file-shield"></i> 2. Projetos & Contratos <span id="badge-def-cont" class="subtab-badge">0</span> </button> <button onclick="abrirAbaDefenseEspecifica('clientes')" id="subbtn-def-clientes" class="subtab-btn"> <i class="fa-solid fa-building-shield"></i> 3. Clientes Defense <span id="badge-def-cli" class="subtab-badge">0</span> </button> <button onclick="abrirAbaDefenseEspecifica('estoque')" id="subbtn-def-estoque" class="subtab-btn"> <i class="fa-solid fa-boxes-stacked"></i> 4. Estoque Defense <span id="badge-def-est" class="subtab-badge">0</span> </button> </div> </div> <!-- KPIs Globais da Subsidiária LES Defense --> <div id="def-kpis-grid" class="grid grid-cols-1 md:grid-cols-5 gap-4"></div> <!-- SUB-ABA 1: CONSULTORIA & DIAGNÓSTICO DE ESPAÇO AÉREO (SITE SURVEY) --> <div id="subtab-def-consultoria" class="subtab-def subtab-anim space-y-6"> <div class="les-card p-6 space-y-4" id="card-form-def-consultoria"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-3"> <div> <span id="def-cons-mode-badge" class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Diagnóstico B2B • Apenas Equipamentos de Detecção Passiva (Conformidade ANATEL / DECEA)</span> <h3 id="def-cons-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Registrar / Editar Consultoria & Diagnóstico de Vulnerabilidade (Site Survey)</h3> </div> <button type="button" id="btn-cancelar-edicao-def-cons" onclick="cancelarEdicaoConsultoriaDefense()" class="hidden px-3 py-1.5 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg text-xs font-bold"> Cancelar Edição </button> </div> <form id="form-def-consultoria" onsubmit="salvarConsultoriaDefense(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="def-cons-edit-id" value=""> <div> <label class="font-semibold block mb-1">Código do Diagnóstico *</label> <input type="text" id="def-cons-id" required placeholder="Ex: DIAG-DEF-001" class="w-full border rounded-lg px-2.5 py-2 font-mono font-bold"> </div> <div> <label class="font-semibold block mb-1">Data da Vistoria / Estudo *</label> <input type="date" id="def-cons-data" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <div class="flex justify-between items-center mb-1"> <label class="font-semibold">Cliente LES Defense *</label> <button type="button" onclick="abrirAbaDefenseEspecifica('clientes')" class="text-[10px] font-bold text-emerald-700 hover:underline">+ Novo Cliente</button> </div> <select id="def-cons-cliente" required class="w-full border rounded-lg px-2.5 py-2 bg-white"></select> </div> <div> <label class="font-semibold block mb-1">Status da Consultoria *</label> <select id="def-cons-status" class="w-full border rounded-lg px-2.5 py-2 font-bold bg-white"> <option value="1. Vistoria Agendada">1. Vistoria Agendada</option> <option value="2. Em Análise de Espectro / Site Survey">2. Em Análise de Espectro / Site Survey</option> <option value="3. Laudo Emitido / Entregue">3. Laudo Emitido / Entregue</option> <option value="4. Convertido em Projeto/Contrato">4. Convertido em Projeto/Contrato</option> </select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Planta / Unidade Avaliada & Cidade-UF *</label> <input type="text" id="def-cons-planta" required placeholder="Ex: Complexo Industrial / Refinaria / Centro Logístico — Anápolis-GO" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Raio / Perímetro de Detecção *</label> <input type="text" id="def-cons-raio" required value="Raio de 3 km a 5 km (Perímetro Crítico)" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Risco Principal Mapeado *</label> <select id="def-cons-risco" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="Espionagem Industrial / Filmagem Não Autorizada">Espionagem Industrial / Filmagem Não Autorizada</option> <option value="Segurança Patrimonial / Intrusão Perimetral">Segurança Patrimonial / Intrusão Perimetral</option> <option value="Risco Operacional / Segurança de Voo">Risco Operacional / Segurança de Voo</option> <option value="Proteção de Executivos / Eventos Corporativos">Proteção de Executivos / Eventos Corporativos</option> <option value="Infraestrutura Crítica / Energia & Logística">Infraestrutura Crítica / Energia & Logística</option> </select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Arquitetura de Detecção Recomendada (Somente Detecção) *</label> <select id="def-cons-arquitetura" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold text-emerald-950"> <option value="Sensor RF Passivo + Remote ID (Detecção de Drone e Posição do Piloto)">Sensor RF Passivo + Remote ID (Detecção de Drone e Posição do Piloto)</option> <option value="Sensor RF Passivo + Câmera Óptica/Térmica PTZ">Sensor RF Passivo + Câmera Óptica/Térmica PTZ</option> <option value="Radar 3D de Baixa Altitude + Sensor RF Passivo">Radar 3D de Baixa Altitude + Sensor RF Passivo</option> <option value="Rede Multi-Sensor Integrada (RF + Radar + Acústico)">Rede Multi-Sensor Integrada (RF + Radar + Acústico)</option> <option value="Em Fase de Escolha / Homologação de Sensores para o Site">Em Fase de Escolha / Homologação de Sensores para o Site</option> </select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Ambiente RF, Topografia & Obstáculos Locais *</label> <input type="text" id="def-cons-ambiente" required placeholder="Ex: Torres metálicas, galpões, linhas de transmissão, ruído 2.4/5.8 GHz..." class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="md:col-span-3"> <label class="font-semibold block mb-1">Parecer Técnico, Pontos de Instalação & Recomendações de Protocolo *</label> <input type="text" id="def-cons-parecer" required placeholder="Descreva a recomendação de posicionamento dos sensores de detecção e protocolo de alerta para a segurança..." class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Consultor / Engenheiro Responsável *</label> <input type="text" id="def-cons-resp" required value="Lucas Pires / Jean Natividade" class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="md:col-span-4 flex justify-end pt-1"> <button type="submit" id="btn-submit-def-cons" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Consultoria / Diagnóstico </button> </div> </form> </div> <div class="les-card p-5 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <h4 class="text-xs font-bold uppercase text-zinc-900">Diagnósticos & Site Surveys Cadastrados (Emita o Laudo PDF ou Converta em Proposta/Contrato)</h4> <input type="text" id="filtro-def-cons" oninput="renderDefense()" placeholder="Buscar por código, cliente, planta ou arquitetura..." class="border rounded-lg px-3 py-1.5 text-xs w-72"> </div> <div id="lista-def-consultorias" class="space-y-3"></div> </div> </div> <!-- SUB-ABA 2: PROJETOS & CONTRATOS DE SERVIÇO (CONSULTORIA E MONITORAMENTO + COMISSÃO 4%) --> <div id="subtab-def-contratos" class="subtab-def subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4" id="card-form-def-contrato"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-3"> <div> <span id="def-cont-mode-badge" class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Comercial B2B • Contratos Alimentam Exclusivamente o Financeiro da Subsidiária LES Defense</span> <h3 id="def-cont-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Emitir / Editar Proposta ou Contrato de Serviço LES Defense</h3> </div> <button type="button" id="btn-cancelar-edicao-def-cont" onclick="cancelarEdicaoContratoDefense()" class="hidden px-3 py-1.5 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg text-xs font-bold"> Cancelar Edição </button> </div> <form id="form-def-contrato" onsubmit="salvarContratoDefense(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="def-cont-edit-id" value=""> <div> <label class="font-semibold block mb-1">Nº Proposta / Contrato *</label> <input type="text" id="def-cont-id" required placeholder="Ex: DEF-2026-001" class="w-full border rounded-lg px-2.5 py-2 font-mono font-bold"> </div> <div> <label class="font-semibold block mb-1">Data de Emissão *</label> <input type="date" id="def-cont-data" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <div class="flex justify-between items-center mb-1"> <label class="font-semibold">Cliente LES Defense *</label> <button type="button" onclick="abrirAbaDefenseEspecifica('clientes')" class="text-[10px] font-bold text-emerald-700 hover:underline">+ Novo Cliente</button> </div> <select id="def-cont-cliente" required class="w-full border rounded-lg px-2.5 py-2 bg-white"></select> </div> <div> <label class="font-semibold block mb-1 text-emerald-800">Responsável pela Venda *</label> <select id="def-cont-vendedor" required onchange="recalcularPreviewContratoDefense()" class="w-full border-2 border-emerald-500 rounded-lg px-2.5 py-2 bg-emerald-50/40 font-bold"> <option value="Jean Natividade">Jean Natividade</option> <option value="Lucas Pires">Lucas Pires</option> <option value="Matheus Azevedo">Matheus Azevedo</option> </select> </div> <div> <label class="font-semibold block mb-1">Diagnóstico Ref. (Site Survey)</label> <select id="def-cont-diag-ref" class="w-full border rounded-lg px-2.5 py-2 bg-white font-mono"> <option value="">-- Sem diagnóstico vinculado --</option> </select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Modalidade de Serviço LES Defense *</label> <select id="def-cont-modalidade" class="w-full border rounded-lg px-2.5 py-2 bg-white font-bold text-zinc-900"> <option value="Consultoria Técnica & Diagnóstico de Espaço Aéreo (Site Survey)">Consultoria Técnica & Diagnóstico de Espaço Aéreo (Site Survey)</option> <option value="Monitoramento Temporário de Eventos / Operações Críticas (Detecção Passiva)">Monitoramento Temporário de Eventos / Operações Críticas (Detecção Passiva)</option> <option value="Monitoramento Contínuo 24/7 (Locação de Sensores de Detecção + Alertas)">Monitoramento Contínuo 24/7 (Locação de Sensores de Detecção + Alertas)</option> <option value="Projeto Turnkey de Implantação de Sistema de Detecção Passiva + Treinamento">Projeto Turnkey de Implantação de Sistema de Detecção Passiva + Treinamento</option> </select> </div> <div> <label class="font-semibold block mb-1">Prazo / Vigência *</label> <input type="text" id="def-cont-vigencia" required value="12 Meses" placeholder="Ex: Pontual / 12 Meses / 7 Dias" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Modelo de Faturamento *</label> <select id="def-cont-periodicidade" class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="Valor Único (Projeto / Consultoria)">Valor Único (Projeto / Consultoria)</option> <option value="Mensalidade Recorrente (Monitoramento)">Mensalidade Recorrente (Monitoramento)</option> <option value="Diária / Operação Dedicada">Diária / Operação Dedicada</option> </select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Escopo Técnico de Detecção & Entregáveis *</label> <input type="text" id="def-cont-escopo" required placeholder="Ex: Diagnóstico de espectro RF, instalação de sensores passivos, painel de alertas e relatórios..." class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Valor Cheio do Serviço / Contrato (R$) *</label> <input type="number" step="0.01" id="def-cont-valor" required placeholder="0.00" oninput="recalcularPreviewContratoDefense()" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div> <label class="font-semibold block mb-1">Status Comercial *</label> <select id="def-cont-status" class="w-full border rounded-lg px-2.5 py-2 font-bold bg-white"> <option value="Proposta em Análise">Proposta em Análise</option> <option value="Aprovado / Contrato Ativo">Aprovado / Contrato Ativo</option> <option value="Concluído / Entregue">Concluído / Entregue</option> <option value="Cancelado / Recusado">Cancelado / Recusado</option> </select> </div> <div> <label class="font-semibold block mb-1 text-emerald-900">Forma de Pagamento *</label> <select id="def-cont-forma-pagamento" onchange="recalcularPreviewContratoDefense()" class="w-full border-2 border-emerald-500 rounded-lg px-2.5 py-2 bg-white font-bold"> <option value="PIX">PIX — 5% de Desconto s/ Valor Cheio</option> <option value="CARTAO">Cartão de Crédito — Até 4x Sem Juros</option> <option value="DINHEIRO">Dinheiro — Valor Cheio</option> </select> </div> <div> <label class="font-semibold block mb-1 text-sky-900">Parcelas no Cartão</label> <select id="def-cont-parcelas-cartao" onchange="recalcularPreviewContratoDefense()" class="w-full border border-sky-400 rounded-lg px-2.5 py-2 bg-white font-bold text-sky-950"> <option value="1">1x Sem Juros</option> <option value="2">2x Sem Juros</option> <option value="3">3x Sem Juros</option> <option value="4" selected>4x Sem Juros</option> <option value="5">5x no Cartão</option> <option value="6">6x no Cartão</option> <option value="7">7x no Cartão</option> <option value="8">8x no Cartão</option> <option value="9">9x no Cartão</option> <option value="10">10x no Cartão</option> <option value="11">11x no Cartão</option> <option value="12">12x no Cartão</option> </select> </div> <div> <label class="font-semibold block mb-1 text-zinc-700">Retorno do Responsável</label> <input type="text" id="def-cont-comissao-preview" readonly value="R$ 0,00 (4%)" class="w-full border-2 border-amber-400 rounded-lg px-2.5 py-2 bg-amber-50 text-amber-950 font-mono font-black"> </div> <div class="md:col-span-4"> <label class="font-semibold block mb-1">Resumo do Modo de Pagamento Selecionado</label> <input type="text" id="def-cont-calculo-preview" readonly value="PIX c/ 5% Desc.: R$ 0,00 | Cartão 4x s/ juros: R$ 0,00 | Comissão 4%: R$ 0,00" class="w-full border rounded-lg px-2.5 py-2 bg-zinc-950 text-emerald-400 font-mono font-bold"> </div> <div class="md:col-span-4 flex justify-end pt-1"> <button type="submit" id="btn-submit-def-cont" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-file-signature mr-1.5 text-emerald-400"></i> Salvar Proposta / Contrato LES Defense </button> </div> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Nº Contrato / Data</th> <th class="p-3">Cliente & Vendedor Resp.</th> <th class="p-3">Modalidade & Escopo de Detecção</th> <th class="p-3">Vigência / Faturamento</th> <th class="p-3">Valor (R$)</th> <th class="p-3">Status & Financeiro Defense</th> <th class="p-3 text-right">Ações (PDF / Editar / Aprovar)</th> </tr> </thead> <tbody id="tabela-def-contratos" class="divide-y"></tbody> </table> </div> </div> <!-- SUB-ABA / ABA 3: CLIENTES EXCLUSIVOS LES DEFENSE (BASE 100% SEPARADA DE DRONES) --> <div id="subtab-def-clientes" class="subtab-def subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4" id="card-form-def-cliente"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-3"> <div> <span id="def-cli-mode-badge" class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Carteira Comercial B2B • LES Defense</span> <h3 id="def-cli-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Cadastrar / Editar Cliente Corporativo ou Institucional — LES Defense</h3> <p class="text-xs text-zinc-500">Gestão de clientes corporativos, plantas industriais e contatos de segurança patrimonial.</p> </div> <button type="button" id="btn-cancelar-edicao-def-cli" onclick="cancelarEdicaoClienteDefense()" class="hidden px-3 py-1.5 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg text-xs font-bold"> Cancelar Edição </button> </div> <form id="form-def-cliente" onsubmit="salvarClienteDefense(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="def-cli-edit-id" value=""> <div> <label class="font-semibold block mb-1">Contato / Gestor de Segurança *</label> <input type="text" id="def-cli-contato" required placeholder="Ex: Cel. Renato / Diretor de Segurança" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Empresa / Razão Social / Órgão *</label> <input type="text" id="def-cli-nome" required placeholder="Razão Social da Empresa" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div> <label class="font-semibold block mb-1">Nome Fantasia / Sigla</label> <input type="text" id="def-cli-fantasia" placeholder="Nome Fantasia" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">CNPJ / Identificação *</label> <input type="text" id="def-cli-cnpj" required placeholder="00.000.000/0001-00" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Telefone / WhatsApp *</label> <input type="text" id="def-cli-whatsapp" required placeholder="(00) 90000-0000" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">E-mail Corporativo *</label> <input type="email" id="def-cli-email" required placeholder="seguranca@empresa.com.br" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Cidade / UF *</label> <input type="text" id="def-cli-cidade" required value="Anápolis - GO" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Segmento / Infraestrutura *</label> <select id="def-cli-segmento" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="Infraestrutura Crítica / Indústria & Logística">Infraestrutura Crítica / Indústria & Logística</option> <option value="Energia, Refinaria & Mineração">Energia, Refinaria & Mineração</option> <option value="Portos, Aeroportos & Recintos Alfandegados">Portos, Aeroportos & Recintos Alfandegados</option> <option value="Eventos Corporativos & Proteção VIP">Eventos Corporativos & Proteção VIP</option> <option value="Segurança Pública, Defesa & Instalações">Segurança Pública, Defesa & Instalações</option> <option value="Condomínio Empresarial / Farmacêutico">Condomínio Empresarial / Farmacêutico</option> </select> </div> <div> <label class="font-semibold block mb-1">Perímetro / Área Estimada</label> <input type="text" id="def-cli-perimetro" value="Raio de 3 km a 5 km" placeholder="Ex: Raio de 3 km a 5 km" class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Endereço da Planta / Unidade Avaliada *</label> <input type="text" id="def-cli-endereco" required placeholder="Rodovia / Rua, Nº, Distrito Industrial, Cidade - UF, CEP" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Observações / Requisitos NDA</label> <input type="text" id="def-cli-obs" placeholder="Notas de confidencialidade ou acesso..." class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="md:col-span-4 flex justify-end pt-1"> <button type="submit" id="btn-submit-def-cli" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-building-shield mr-1.5 text-emerald-400"></i> Salvar Cliente LES Defense </button> </div> </form> </div> <div class="les-card p-5 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <h4 class="text-xs font-bold uppercase text-zinc-900">Carteira de Clientes Exclusiva da Subsidiária LES Defense</h4> <input type="text" id="filtro-def-clientes" oninput="renderDefense()" placeholder="Buscar cliente Defense por nome, CNPJ, contato ou cidade..." class="border rounded-lg px-3 py-1.5 text-xs w-72"> </div> <div class="overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Empresa / Órgão Cliente</th> <th class="p-3">CNPJ</th> <th class="p-3">Gestor / Contato</th> <th class="p-3">Segmento & Perímetro</th> <th class="p-3">Planta / Endereço</th> <th class="p-3 text-right">Ações</th> </tr> </thead> <tbody id="tabela-def-clientes" class="divide-y"></tbody> </table> </div> </div> </div> <!-- SUB-ABA 4: ESTOQUE EXCLUSIVO LES DEFENSE (SENSORES, RADARES, MASTROS & HARDWARE TÁTICO) --> <div id="subtab-def-estoque" class="subtab-def subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4" id="card-form-def-estoque"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-3"> <div> <span id="def-est-mode-badge" class="text-[10px] uppercase tracking-widest text-amber-700 font-bold block">Almoxarifado Técnico • LES Defense (Conta Contábil 1.1.03-DEF)</span> <h3 id="def-est-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Cadastrar / Editar Equipamento ou Componente no Estoque LES Defense</h3> <p class="text-xs text-zinc-500">Controle de sensores passivos, mastros táticos, números de série e saldo contábil (Conta 1.1.03-DEF).</p> </div> <div class="flex items-center gap-2"> <button type="button" id="btn-cancelar-edicao-def-est" onclick="cancelarEdicaoEstoqueDefense()" class="hidden px-3 py-1.5 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg text-xs font-bold"> Cancelar Edição </button> <button type="button" onclick="gerarPDFAuditoriaEstoqueDefense()" class="bg-zinc-950 hover:bg-zinc-800 text-white px-3.5 py-2 rounded-xl text-xs font-bold shadow flex items-center gap-1.5"> <i class="fa-solid fa-file-pdf text-emerald-400"></i> PDF Auditoria Estoque Defense </button> </div> </div> <form id="form-def-estoque" onsubmit="salvarItemEstoqueDefense(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="def-est-edit-id" value=""> <div> <label class="font-semibold block mb-1">P/N ou Código Tático Defense *</label> <input type="text" id="def-est-pn" required placeholder="Ex: DEF-RF-PASSIVE-5K" class="w-full border rounded-lg px-2.5 py-2 font-mono font-bold"> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Descrição do Equipamento / Sensor *</label> <input type="text" id="def-est-desc" required placeholder="Ex: Sensor RF Passivo Omnidirecional + Receptor Remote ID (3 a 5 km)" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Categoria Defense *</label> <select id="def-est-categoria" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="Sensor RF Passivo / Remote ID">Sensor RF Passivo / Remote ID</option> <option value="Radar 3D de Baixa Altitude">Radar 3D de Baixa Altitude</option> <option value="Câmera Óptica / Térmica PTZ">Câmera Óptica / Térmica PTZ</option> <option value="Mastro Tático / Tripé / Suporte IP67">Mastro Tático / Tripé / Suporte IP67</option> <option value="Energia PoE / Bateria / Starlink / Comms">Energia PoE / Bateria / Starlink / Comms</option> <option value="Kit Maleta de Site Survey">Kit Maleta de Site Survey</option> </select> </div> <div> <label class="font-semibold block mb-1">Quantidade Disponível *</label> <input type="number" step="1" min="0" id="def-est-qtd" required value="1" oninput="recalcularPreviewEstoqueDefense()" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div> <label class="font-semibold block mb-1">Estoque Mínimo (Alerta)</label> <input type="number" step="1" min="0" id="def-est-min" value="1" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Serial Number (S.N.) / Lote</label> <input type="text" id="def-est-sn" placeholder="Ex: SN-DEF-2026-01" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Status Operacional *</label> <select id="def-est-status" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="Disponível na Base Defense">Disponível na Base Defense</option> <option value="Alocado em Contrato / Cliente">Alocado em Contrato / Cliente</option> <option value="Em Campo (Site Survey)">Em Campo (Site Survey)</option> <option value="Em Calibração / Bancada">Em Calibração / Bancada</option> </select> </div> <div> <label class="font-semibold block mb-1">Data de Entrada / Aquisição *</label> <input type="date" id="def-est-entrada" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Próx. Calibração / Revisão</label> <input type="date" id="def-est-validade" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Localização / Alocação Atual *</label> <input type="text" id="def-est-local" required value="Base LES Defense • Anápolis-GO (Sala Cofre / Sensores)" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Doc. Origem / NF / Projeto</label> <input type="text" id="def-est-doc" placeholder="Ex: INV-DEF-01" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Custo Unitário Contábil (R$) *</label> <input type="number" step="0.01" min="0" id="def-est-custo" required value="0" oninput="recalcularPreviewEstoqueDefense()" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div> <label class="font-semibold block mb-1">Valor Contábil Total (1.1.03-DEF)</label> <input type="text" id="def-est-total-preview" readonly value="R$ 0,00" class="w-full border rounded-lg px-2.5 py-2 bg-zinc-950 text-emerald-400 font-extrabold"> </div> <div class="md:col-span-2 flex items-end justify-end"> <button type="submit" id="btn-submit-def-est" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-boxes-packing mr-1.5 text-emerald-400"></i> Salvar no Estoque Defense & Contabilizar </button> </div> </form> </div> <div class="les-card p-5 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <h4 class="text-xs font-bold uppercase text-zinc-900">Inventário Exclusivo da Subsidiária LES Defense</h4> <input type="text" id="filtro-def-estoque" oninput="renderDefense()" placeholder="Filtrar sensor, P/N, S.N., categoria ou local..." class="border rounded-lg px-3 py-1.5 text-xs w-72"> </div> <div class="overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Código / P/N Defense</th> <th class="p-3">Equipamento & Categoria</th> <th class="p-3">S.N. / Status Operacional</th> <th class="p-3">Localização / Alocação</th> <th class="p-3 text-center whitespace-nowrap">Qtd (ea)</th> <th class="p-3 text-right">Custo Unit. / Total Contábil</th> <th class="p-3 text-right">Ações</th> </tr> </thead> <tbody id="tabela-def-estoque" class="divide-y"></tbody> </table> </div> </div> </div> </section> <!-- ===================================================== --> <!-- MÓDULO DE DESENVOLVIMENTO: TAREFAS, SUBTAREFAS & PROGRESSO --> <!-- ===================================================== --> <section id="tab-desenvolvimento" class="tab-content space-y-6"> <div class="bg-zinc-950 text-white p-5 rounded-xl flex flex-wrap justify-between items-center gap-4 no-print shadow-md border border-zinc-800"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-400 font-bold block"><i class="fa-solid fa-list-check mr-1"></i> Gestão de Desenvolvimento & Engenharia • Tarefas, Subtarefas e Prazos</span> <h2 class="text-sm font-bold mt-0.5">Panorama Geral de Progresso • Etapas de Desenvolvimento • Checklist de Subtarefas & Alertas de Vencimento</h2> </div> <div class="flex flex-wrap items-center gap-2"> <button type="button" onclick="abrirNovaTarefaDevRapida()" class="bg-emerald-500 hover:bg-emerald-400 text-zinc-950 px-3.5 py-2 rounded-xl text-xs font-extrabold shadow flex items-center gap-1.5 transition"> <i class="fa-solid fa-plus"></i> Nova Tarefa / Etapa </button> <button type="button" onclick="gerarPDFRelatorioDesenvolvimento()" class="bg-zinc-800 hover:bg-zinc-700 text-white border border-zinc-700 px-3.5 py-2 rounded-xl text-xs font-bold flex items-center gap-1.5 transition"> <i class="fa-solid fa-file-pdf text-rose-400"></i> Relatório PDF de Progresso </button> </div> </div> <!-- PANORAMA GERAL DE PROGRESSO (BARRA GLOBAL + PROGRESSO POR ETAPA) --> <div class="les-card p-6 space-y-5 border-t-4 border-t-emerald-600"> <div class="flex flex-wrap justify-between items-end gap-4"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-700 font-extrabold block">Panorama Geral do Desenvolvimento</span> <h3 class="text-base font-extrabold text-zinc-950">Evolução Consolidada das Etapas e Subtarefas</h3> <p id="dev-panorama-subtitulo" class="text-xs text-zinc-500 mt-0.5">Calculado automaticamente com base nas tarefas e subtarefas concluídas.</p> </div> <div class="text-right"> <span class="text-[10px] uppercase tracking-widest text-zinc-400 font-bold block">Progresso Global</span> <span id="dev-panorama-percentual" class="text-3xl font-black text-emerald-600">0%</span> </div> </div> <!-- Barra de Progresso Global --> <div class="w-full bg-zinc-200 rounded-full h-4 overflow-hidden shadow-inner p-0.5"> <div id="dev-panorama-barra" class="bg-gradient-to-r from-emerald-600 via-emerald-500 to-teal-400 h-full rounded-full transition-all duration-500" style="width: 0%"></div> </div> <!-- Barras de Progresso por Etapa de Desenvolvimento --> <div id="dev-etapas-progresso-grid" class="grid grid-cols-1 md:grid-cols-4 gap-3 pt-1"></div> </div> <!-- KPIs RÁPIDOS DE DESENVOLVIMENTO & PRAZOS --> <div id="dev-kpis-grid" class="grid grid-cols-1 md:grid-cols-5 gap-4"></div> <!-- RADAR DE TAREFAS PENDENTES COM PRAZOS PRÓXIMOS A VENCER / ATRASADOS --> <div class="les-card p-5 space-y-3 border-l-4 border-l-amber-500"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <div class="flex items-center gap-2"> <span class="w-8 h-8 rounded-lg bg-amber-100 text-amber-800 flex items-center justify-center font-bold"> <i class="fa-solid fa-clock-rotate-left"></i> </span> <div> <h3 class="text-xs font-extrabold uppercase tracking-wider text-zinc-900">Tarefas Pendentes & Prazos Próximos a Vencer</h3> <p class="text-[11px] text-zinc-500">Ordenadas automaticamente pelo prazo mais próximo para facilitar a priorização da equipe</p> </div> </div> <span id="dev-badge-urgentes" class="px-3 py-1 rounded-full text-[11px] font-extrabold bg-amber-100 text-amber-900">0 com vencimento próximo</span> </div> <div id="dev-radar-prazos-lista" class="grid grid-cols-1 md:grid-cols-2 gap-3"></div> </div> <!-- FORMULÁRIO SIMPLES E COMPLETO PARA CRIAR / EDITAR TAREFA E SUBTAREFAS --> <div class="les-card p-6 space-y-4" id="card-form-dev-tarefa"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-3"> <div> <span id="dev-form-mode-badge" class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Cadastro & Edição Completa • Tarefas e Subtarefas</span> <h3 id="dev-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Criar / Editar Tarefa de Desenvolvimento e Subtarefas</h3> </div> <button type="button" id="btn-cancelar-edicao-dev" onclick="cancelarEdicaoTarefaDev()" class="hidden px-3.5 py-1.5 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg text-xs font-bold"> <i class="fa-solid fa-xmark mr-1"></i> Cancelar Edição </button> </div> <form id="form-dev-tarefa" onsubmit="salvarTarefaDev(event)" class="space-y-4 text-xs"> <input type="hidden" id="dev-tar-edit-id" value=""> <div class="grid grid-cols-1 md:grid-cols-4 gap-3"> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Título da Tarefa / Entrega *</label> <input type="text" id="dev-tar-titulo" required placeholder="Ex: Homologar Sensor RF Passivo p/ Detecção até 5 km" class="w-full border rounded-lg px-3 py-2 font-bold text-zinc-900"> </div> <div> <label class="font-semibold block mb-1">Etapa do Desenvolvimento *</label> <select id="dev-tar-etapa" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="1. Pesquisa & Definição de Escopo">1. Pesquisa & Definição de Escopo</option> <option value="2. Escolha & Homologação de Equipamentos">2. Escolha & Homologação de Equipamentos</option> <option value="3. Arquitetura, Suportes & Integração">3. Arquitetura, Suportes & Integração</option> <option value="4. Testes de Campo & Validação Operacional">4. Testes de Campo & Validação Operacional</option> </select> </div> <div> <label class="font-semibold block mb-1">Projeto / Área *</label> <select id="dev-tar-projeto" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="LES Defense — Projeto Anti-Drone">LES Defense — Projeto Anti-Drone</option> <option value="Engenharia & MRO — Drones">Engenharia & MRO — Drones</option> <option value="Novos Produtos & P&D LES">Novos Produtos & P&D LES</option> </select> </div> <div> <label class="font-semibold block mb-1 text-emerald-900">Responsável Principal (Envolvido 1) *</label> <select id="dev-tar-resp" required class="w-full border-2 border-emerald-500 rounded-lg px-2.5 py-2 bg-emerald-50/40 font-bold text-zinc-900"></select> </div> <div> <label class="font-semibold block mb-1 text-sky-900">Responsável Secundário (Envolvido 2)</label> <select id="dev-tar-resp-sec" class="w-full border border-sky-400 rounded-lg px-2.5 py-2 bg-sky-50/40 font-semibold text-zinc-900"></select> </div> <div> <label class="font-semibold block mb-1">Prazo / Vencimento *</label> <input type="date" id="dev-tar-prazo" required class="w-full border rounded-lg px-2.5 py-2 font-semibold"> </div> <div> <label class="font-semibold block mb-1">Prioridade *</label> <select id="dev-tar-prioridade" class="w-full border rounded-lg px-2.5 py-2 bg-white font-semibold"> <option value="Alta">Alta / Crítica</option> <option value="Média" selected>Média</option> <option value="Normal">Normal</option> </select> </div> <div class="md:col-span-4"> <label class="font-semibold block mb-1">Observações / Notas Técnicas da Tarefa</label> <input type="text" id="dev-tar-notas" placeholder="Detalhes, fornecedores avaliados, links ou requisitos... (Visível apenas para os responsáveis selecionados acima)" class="w-full border rounded-lg px-2.5 py-2"> </div> </div> <!-- Subtarefas dinâmicas dentro do formulário --> <div class="bg-zinc-50 p-4 rounded-xl border border-zinc-200 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2"> <div> <h4 class="font-bold uppercase text-zinc-900 text-xs"><i class="fa-solid fa-list-check mr-1 text-emerald-600"></i> Subtarefas desta Etapa (Com Valor Estimado, Caixa de Texto e Anexo Opcional)</h4> <p class="text-[11px] text-zinc-500">Informe o título e o valor estimado (R$) de cada subtarefa. Após criar, clique em qualquer subtarefa para abrir sua janela exclusiva com caixa de texto e anexação de documentos.</p> </div> <button type="button" onclick="adicionarLinhaSubtarefaForm('', false)" class="bg-zinc-900 hover:bg-zinc-800 text-white px-3 py-1.5 rounded-lg text-[11px] font-bold"> <i class="fa-solid fa-plus mr-1 text-emerald-400"></i> Adicionar Subtarefa </button> </div> <div id="dev-form-subtarefas-container" class="space-y-2"></div> </div> <div class="flex justify-end gap-2"> <button type="submit" id="btn-submit-dev-tarefa" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-bold shadow"> <i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Tarefa & Subtarefas </button> </div> </form> </div> <!-- FILTROS E LISTA VISUAL DE TAREFAS E SUBTAREFAS --> <div class="les-card p-6 space-y-4"> <div class="flex flex-wrap justify-between items-center gap-3 border-b pb-3"> <div class="flex flex-wrap items-center gap-1.5"> <button type="button" onclick="setFiltroStatusDev('TODAS')" id="btn-fdev-TODAS" class="px-3 py-1.5 rounded-lg text-xs font-bold bg-zinc-950 text-white">Todas</button> <button type="button" onclick="setFiltroStatusDev('PENDENTES')" id="btn-fdev-PENDENTES" class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-zinc-100 text-zinc-700 hover:bg-zinc-200">Pendentes</button> <button type="button" onclick="setFiltroStatusDev('PROXIMAS')" id="btn-fdev-PROXIMAS" class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-amber-50 text-amber-900 border border-amber-200 hover:bg-amber-100">Prazos Próximos / Atrasadas</button> <button type="button" onclick="setFiltroStatusDev('CONCLUIDAS')" id="btn-fdev-CONCLUIDAS" class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-emerald-50 text-emerald-900 border border-emerald-200 hover:bg-emerald-100">Concluídas</button> </div> <div class="flex flex-wrap items-center gap-2"> <select id="filtro-dev-etapa" onchange="renderDesenvolvimento()" class="border rounded-lg px-2.5 py-1.5 text-xs bg-white font-semibold"> <option value="">Todas as Etapas</option> <option value="1. Pesquisa & Definição de Escopo">1. Pesquisa & Definição de Escopo</option> <option value="2. Escolha & Homologação de Equipamentos">2. Escolha & Homologação de Equipamentos</option> <option value="3. Arquitetura, Suportes & Integração">3. Arquitetura, Suportes & Integração</option> <option value="4. Testes de Campo & Validação Operacional">4. Testes de Campo & Validação Operacional</option> </select> <input type="text" id="filtro-dev-busca" oninput="renderDesenvolvimento()" placeholder="Buscar tarefa, subtarefa ou responsável..." class="border rounded-lg px-3 py-1.5 text-xs w-64"> </div> </div> <div id="lista-dev-tarefas" class="space-y-4"></div> </div> <!-- AMBIENTE EXPERIMENTAL: SANDBOX EM DESENVOLVIMENTO --> <div class="les-card p-5 bg-gradient-to-r from-zinc-950 via-zinc-900 to-zinc-950 text-white border border-zinc-800 flex flex-wrap justify-between items-center gap-4"> <div class="flex items-center gap-3.5"> <div class="w-11 h-11 rounded-xl bg-amber-500/15 border border-amber-500/40 text-amber-400 flex items-center justify-center shrink-0"> <i class="fa-solid fa-flask-vial text-lg"></i> </div> <div> <span class="text-[10px] uppercase tracking-widest text-amber-400 font-extrabold block">Ambiente Experimental • Pesquisa & Desenvolvimento</span> <h4 class="text-sm font-bold">Laboratório de Modelos 3D & Prototipagem Assistida por IA</h4> <p class="text-xs text-zinc-400 mt-0.5">Módulo experimental para visualização interativa 3D (.STL / .GLB / .OBJ) e prototipagem assistida por IA para suportes de sensores, torres e casulos.</p> </div> </div> <span class="px-3.5 py-1.5 rounded-lg bg-amber-500/15 border border-amber-500/40 text-[11px] font-mono font-bold text-amber-300"> <i class="fa-solid fa-flask mr-1.5"></i> Sandbox em Desenvolvimento </span> </div> </section> <!-- ===================================================== --> <!-- MÓDULO 4: LOGÍSTICA — P.O. & RASTREAMENTO DE PEDIDOS --> <!-- ===================================================== --> <section id="tab-logistica" class="tab-content space-y-6"> <div class="bg-white border border-zinc-200 p-5 rounded-xl flex flex-wrap justify-between items-center gap-4 no-print shadow-sm"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Logística Internacional, Purchase Orders & Rastreamento Completo de Pedidos</span> <h2 class="text-sm font-bold text-zinc-900 mt-0.5">Purchase Orders (P.O.) • Rastreamento Visual de Pedidos (Despacho, Trânsito, Desembaraço & Entrega) • Fornecedores</h2> </div> <div class="subtab-pill-bar"> <button onclick="switchSubTab('log', 'tracking')" id="subbtn-log-tracking" class="subtab-btn active-sub"> <i class="fa-solid fa-route"></i> 1. Rastreamento de Pedidos <span id="badge-log-trk" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('log', 'pos')" id="subbtn-log-pos" class="subtab-btn"> <i class="fa-solid fa-file-invoice"></i> 2. Emitir / Gerenciar P.O. <span id="badge-log-pos" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('log', 'fornecedores')" id="subbtn-log-fornecedores" class="subtab-btn"> <i class="fa-solid fa-globe"></i> 3. Fornecedores Internacionais </button> </div> </div> <!-- Sub-aba 4.1: RASTREAMENTO VISUAL DE PEDIDOS (TRACKING LOGÍSTICO) --> <div id="subtab-log-tracking" class="subtab-log subtab-anim space-y-6"> <div id="log-kpis-grid" class="grid grid-cols-2 md:grid-cols-5 gap-4"></div> <div class="les-card p-6 space-y-4"> <div class="flex flex-wrap justify-between items-center gap-3 border-b pb-3"> <div> <span class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Monitoramento em Tempo Real • Onde está cada pedido e em qual processo</span> <h3 class="text-sm font-bold uppercase text-zinc-900">Painel de Rastreamento Logístico e Aduaneiro de Pedidos (P.O.)</h3> </div> <input type="text" id="filtro-tracking-po" oninput="renderTrackingCards()" placeholder="Buscar por PO, Cliente, AWB, Local ou Status..." class="border rounded-lg px-3 py-1.5 text-xs w-72"> </div> <div id="lista-tracking-cards" class="space-y-4"></div> </div> </div> <!-- Sub-aba 4.2: Cadastro e Tabela de P.O.s --> <div id="subtab-log-pos" class="subtab-log subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4"> <div class="flex justify-between items-center border-b pb-2"> <h3 id="po-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Emitir / Editar Purchase Order (P.O.)</h3> <button type="button" id="btn-cancelar-edicao-po" onclick="cancelarEdicaoPO()" class="hidden px-3 py-1 bg-zinc-200 text-zinc-800 rounded-lg text-xs font-bold">Cancelar Edição</button> </div> <form id="form-po" onsubmit="salvarPO(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="po-edit-original-num" value=""> <input type="text" id="po-numero" required placeholder="Nº PO (PO-2026-001) *" class="border rounded-lg px-2.5 py-2 font-mono font-bold"> <input type="text" id="po-ref-venda" required placeholder="Ref. Cotação/Venda *" class="border rounded-lg px-2.5 py-2"> <select id="po-cliente" onchange="preencherEnderecoClienteNoPO()" required class="border rounded-lg px-2.5 py-2"></select> <input type="text" id="po-fornecedor" required value="Drone Rescue Systems GmbH" placeholder="Fornecedor *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="po-produto" required placeholder="Produto / P/N *" class="border rounded-lg px-2.5 py-2"> <input type="number" id="po-qtd" required value="1" class="border rounded-lg px-2.5 py-2"> <input type="number" step="0.01" id="po-valor-num" required placeholder="Valor Total (R$) *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="po-incoterm" value="EXW Graz / Áustria -> Brasil" placeholder="Incoterm" class="border rounded-lg px-2.5 py-2"> <input type="date" id="po-emissao" required class="border rounded-lg px-2.5 py-2"> <input type="date" id="po-previsao" required class="border rounded-lg px-2.5 py-2"> <div class="md:col-span-2"> <input type="text" id="po-endereco-entrega" required placeholder="Endereço de Entrega do Cliente *" class="w-full border rounded-lg px-2.5 py-2 bg-zinc-50"> </div> <select id="po-etapa-inicial" class="border rounded-lg px-2.5 py-2 font-semibold md:col-span-2"> <option value="1_PEDIDO_RECEBIDO">1. Pedido Recebido / Dossiê Comercial</option> <option value="2_PO_EMITIDO">2. P.O. Emitido ao Fabricante</option> <option value="3_EM_PRODUCAO">3. Em Produção no Fabricante</option> <option value="4_DESPACHADO_ORIGEM">4. Já Despachado / Pronto p/ Embarque</option> <option value="5_TRANSITO_INTERNACIONAL">5. Em Trânsito Internacional</option> <option value="6_CHEGADA_BRASIL">6. Chegada no Brasil / Recinto Alfandegado</option> <option value="7_EM_DESEMBARACO">7. Em Desembaraço Aduaneiro</option> <option value="8_DESEMBARACADO">8. Já Desembaraçado / Liberado</option> <option value="9_TRANSPORTE_NACIONAL">9. Em Transporte Nacional</option> <option value="10_ENTREGUE">10. Entregue / Concluído</option> </select> <button type="submit" id="btn-submit-po" class="bg-zinc-950 text-white rounded-lg font-semibold py-2 md:col-span-2">Salvar P.O. & Iniciar Rastreamento</button> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Nº P.O. / Ref.</th> <th class="p-3">Cliente & Endereço de Entrega</th> <th class="p-3">Fornecedor / Produtos</th> <th class="p-3">Valor Total</th> <th class="p-3">Dossiê Comercial (Vendas)</th> <th class="p-3">Onde está / Etapa Logística</th> <th class="p-3 text-right">Rastrear / PDF / Editar</th> </tr> </thead> <tbody id="tabela-pos" class="divide-y"></tbody> </table> </div> </div> <div id="subtab-log-fornecedores" class="subtab-log subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4"> <form onsubmit="salvarFornecedor(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="text" id="forn-nome" required placeholder="Fornecedor *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="forn-pais" required placeholder="País *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="forn-contato" required placeholder="Contato *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="forn-moeda" required placeholder="Moeda *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="forn-prazos" placeholder="Prazos" class="border rounded-lg px-2.5 py-2 md:col-span-2"> <input type="text" id="forn-produtos" placeholder="Produtos" class="border rounded-lg px-2.5 py-2"> <button type="submit" class="bg-zinc-950 text-white rounded-lg font-semibold py-2">Cadastrar Fornecedor</button> </form> <table class="w-full text-left text-xs"><tbody id="tabela-fornecedores" class="divide-y"></tbody></table> </div> </div> </section> <!-- ===================================================== --> <!-- MÓDULO 5: ESTOQUE & AUDITORIA CONTÁBIL (NOVO MÓDULO) --> <!-- ===================================================== --> <section id="tab-estoque" class="tab-content space-y-6"> <div class="bg-white border border-zinc-200 p-5 rounded-xl flex flex-wrap justify-between items-center gap-4 no-print shadow-sm"> <div> <span class="text-[10px] uppercase tracking-widest text-amber-600 font-bold block">Almoxarifado Aeronáutico • Interligado ao Balanço Patrimonial & DRE</span> <h2 class="text-sm font-bold text-zinc-900 mt-0.5">Controle de Estoque por P/N, Lote, Validade e S.N. • Movimentação Contábil • Relatório PDF de Auditoria</h2> </div> <div class="flex flex-wrap items-center gap-2"> <div class="subtab-pill-bar"> <button onclick="switchSubTab('est', 'posicao')" id="subbtn-est-posicao" class="subtab-btn active-sub"> <i class="fa-solid fa-boxes-stacked"></i> 1. Posição de Estoque & Cadastro <span id="badge-est-itens" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('est', 'kardex')" id="subbtn-est-kardex" class="subtab-btn"> <i class="fa-solid fa-arrows-left-right"></i> 2. Movimentações & Ajuste Contábil <span id="badge-est-mov" class="subtab-badge">0</span> </button> </div> <button type="button" onclick="gerarPDFAuditoriaEstoque()" class="bg-zinc-950 hover:bg-zinc-800 text-white px-4 py-2.5 rounded-xl text-xs font-bold shadow flex items-center gap-2"> <i class="fa-solid fa-file-pdf text-rose-400"></i> Relatório PDF de Auditoria de Estoque </button> </div> </div> <!-- KPIs do Estoque & Efeito Contábil --> <div id="est-kpis-grid" class="grid grid-cols-1 md:grid-cols-5 gap-4"></div> <!-- Sub-aba 5.1: Posição de Estoque e Cadastro --> <div id="subtab-est-posicao" class="subtab-est subtab-anim space-y-6"> <div class="les-card p-6 space-y-4"> <div class="flex flex-wrap justify-between items-center border-b pb-3 gap-2"> <div> <span id="est-form-badge" class="text-[10px] uppercase tracking-widest text-amber-700 font-bold block">Entrada de Item / Lote com Reflexo Contábil Automático (Conta 1.1.03 - Estoques)</span> <h3 id="est-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Cadastrar / Editar Item em Estoque (P/N, Quantidade, Lote, Validade e S.N.)</h3> </div> <button type="button" id="btn-cancelar-edicao-est" onclick="cancelarEdicaoEstoque()" class="hidden px-3 py-1 bg-zinc-200 text-zinc-800 rounded-lg text-xs font-bold">Cancelar Edição</button> </div> <form id="form-estoque" onsubmit="salvarItemEstoque(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="est-edit-id" value=""> <div> <label class="font-semibold block mb-1">Part Number (P/N) *</label> <select id="est-pn" onchange="aoSelecionarPNEstoque()" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-mono"></select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Nome / Descrição do Item *</label> <input type="text" id="est-desc" required placeholder="Preenchido automaticamente pelo P/N ou digite" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Categoria de Almoxarifado *</label> <select id="est-categoria" class="w-full border rounded-lg px-2.5 py-2"> <option value="Sistema de Recuperação (Paraquedas)">Sistema de Recuperação (Paraquedas)</option> <option value="Pirotécnico / Cápsula / Acionador">Pirotécnico / Cápsula / Acionador</option> <option value="Peças de Reposição / MRO">Peças de Reposição / MRO</option> <option value="Drone / Aeronave RPAS">Drone / Aeronave RPAS</option> <option value="Ferramental / Bancada">Ferramental / Bancada</option> </select> </div> <div> <label class="font-semibold block mb-1">Quantidade em Estoque *</label> <input type="number" step="1" min="0" id="est-qtd" required value="1" oninput="recalcularPreviewContabilEstoque()" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div> <label class="font-semibold block mb-1">Estoque Mínimo (Alerta)</label> <input type="number" step="1" min="0" id="est-qtd-min" value="1" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Lote (Caso tenha)</label> <input type="text" id="est-lote" placeholder="Ex: LOTE-DRS-2026-04 (Opcional)" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Serial Number / S.N. (Caso tenha)</label> <input type="text" id="est-sn" placeholder="Ex: SN-2026-0190 (Opcional)" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Data de Entrada / Fabricação *</label> <input type="date" id="est-entrada" onchange="calcularValidadeAutomaticaEstoque()" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Validade / Shelf Life (Caso tenha)</label> <input type="date" id="est-validade" class="w-full border rounded-lg px-2.5 py-2 bg-zinc-50"> </div> <div> <label class="font-semibold block mb-1">Localização Física / Base *</label> <input type="text" id="est-local" required value="Itajaí-SC • Galpão 06 / Almoxarifado" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Doc. Origem / P.O. / NF</label> <input type="text" id="est-doc-origem" placeholder="Ex: PO-0104 / Inventário Inicial" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Custo Unitário Contábil (R$) *</label> <input type="number" step="0.01" min="0" id="est-custo-unit" required value="0" oninput="recalcularPreviewContabilEstoque()" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Valor Contábil Total (Ativo 1.1.03)</label> <input type="text" id="est-valor-total-preview" readonly value="R$ 0,00" class="w-full border rounded-lg px-2.5 py-2 bg-zinc-900 text-white font-extrabold"> </div> <div class="md:col-span-2 flex items-end justify-end"> <button type="submit" id="btn-submit-estoque" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2.5 rounded-lg font-semibold shadow"> <i class="fa-solid fa-boxes-packing mr-1.5"></i> Salvar no Estoque & Contabilizar no Balanço </button> </div> </form> </div> <div class="les-card p-5 space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2"> <h4 class="text-xs font-bold uppercase text-zinc-900">Inventário Geral de Peças, Sistemas e Lotes em Estoque</h4> <div class="flex gap-2"> <input type="text" id="filtro-estoque" oninput="renderEstoque()" placeholder="Filtrar por PN, Lote, S.N., Descrição ou Local..." class="border rounded-lg px-3 py-1.5 text-xs w-72"> <button type="button" onclick="gerarPDFAuditoriaEstoque()" class="bg-emerald-600 hover:bg-emerald-700 text-white px-3 py-1.5 rounded-lg text-xs font-bold"> <i class="fa-solid fa-print mr-1"></i> Emitir PDF p/ Auditoria </button> </div> </div> <div class="overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Código / P/N</th> <th class="p-3">Descrição & Categoria</th> <th class="p-3">Lote & S.N.</th> <th class="p-3">Validade / Shelf Life</th> <th class="p-3">Localização</th> <th class="p-3 text-center whitespace-nowrap">Qtd (ea)</th> <th class="p-3 text-right">Custo Unit. / Total Contábil</th> <th class="p-3 text-right">Ações / Auditoria</th> </tr> </thead> <tbody id="tabela-estoque" class="divide-y"></tbody> </table> </div> </div> </div> <!-- Sub-aba 5.2: Movimentações de Estoque e Ajustes de Auditoria (Kardex Contábil) --> <div id="subtab-est-kardex" class="subtab-est subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4"> <h3 class="text-sm font-bold uppercase text-zinc-900">Registrar Movimentação Física ou Ajuste de Auditoria (Gera Reflexo Contábil no Financeiro/DRE)</h3> <form onsubmit="registrarMovimentacaoEstoque(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Item / Lote do Estoque *</label> <select id="mov-item-id" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-mono"></select> </div> <div> <label class="font-semibold block mb-1">Tipo de Operação Contábil *</label> <select id="mov-tipo" required class="w-full border rounded-lg px-2.5 py-2 font-semibold"> <option value="ENTRADA_COMPRA">➕ Entrada por Compra / Importação (+Ativo Estoque)</option> <option value="SAIDA_VENDA_CPV">➖ Baixa por Venda Entregue (-Estoque / +CPV na DRE)</option> <option value="SAIDA_MRO">🔧 Baixa p/ Ordem de Serviço MRO (-Estoque / +Custo MRO)</option> <option value="AJUSTE_SOBRA">📈 Ajuste Positivo de Auditoria (+Estoque)</option> <option value="AJUSTE_PERDA">📉 Ajuste Negativo / Perda / Vencimento (-Estoque / +Despesa)</option> </select> </div> <div> <label class="font-semibold block mb-1">Quantidade Movimentada *</label> <input type="number" step="1" min="1" id="mov-qtd" required value="1" class="w-full border rounded-lg px-2.5 py-2 font-bold"> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Motivo / Documento / Referência de Auditoria *</label> <input type="text" id="mov-motivo" required placeholder="Ex: Baixa referente à entrega do PO-0104 / Conferência física mensal" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Data da Movimentação *</label> <input type="date" id="mov-data" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="flex items-end"> <button type="submit" class="w-full bg-zinc-950 text-white py-2 rounded-lg font-semibold">Confirmar & Contabilizar</button> </div> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-3">Data / ID</th> <th class="p-3">P/N & Lote/S.N.</th> <th class="p-3">Operação</th> <th class="p-3 text-center whitespace-nowrap">Qtd (ea)</th> <th class="p-3">Impacto Contábil (R$)</th> <th class="p-3">Justificativa / Doc</th> <th class="p-3">Responsável</th> </tr> </thead> <tbody id="tabela-kardex" class="divide-y"></tbody> </table> </div> </div> </section> <!-- ===================================================== --> <!-- MÓDULO 6: PÓS-VENDA E MRO (INTERFACE LIMPA & DIRETA) --> <!-- ===================================================== --> <section id="tab-posvenda" class="tab-content space-y-6"> <div class="bg-white border border-zinc-200 p-4 rounded-xl flex flex-wrap justify-between items-center gap-3 no-print shadow-sm"> <div> <span class="text-[10px] uppercase tracking-widest text-emerald-700 font-bold block">Pós-venda e MRO</span> <h2 class="text-sm font-bold text-zinc-900">Acompanhamento de P.O. • Demandas Integradas • Ordens de Serviço (O.S.) • Frota S.N. • Manuais Técnicos</h2> </div> <div class="subtab-pill-bar"> <button onclick="switchSubTab('pos', 'pos_clientes')" id="subbtn-pos-pos_clientes" class="subtab-btn active-sub"> <i class="fa-solid fa-boxes-packing"></i> 1. P.O.s & Clientes <span id="badge-pos-poscli" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('pos', 'acionamentos')" id="subbtn-pos-acionamentos" class="subtab-btn"> <i class="fa-solid fa-headset"></i> 2. Demandas <span id="badge-pos-dem" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('pos', 'mro_drones')" id="subbtn-pos-mro_drones" class="subtab-btn"> <i class="fa-solid fa-screwdriver-wrench"></i> 3. Ordens de Serviço (O.S.) <span id="badge-pos-mro" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('pos', 'frota')" id="subbtn-pos-frota" class="subtab-btn"> <i class="fa-solid fa-jet-fighter"></i> 4. Frota S.N. <span id="badge-pos-frota" class="subtab-badge">0</span> </button> <button onclick="switchSubTab('pos', 'integracao')" id="subbtn-pos-integracao" class="subtab-btn"> <i class="fa-solid fa-puzzle-piece"></i> 5. Integração </button> <button onclick="switchSubTab('pos', 'logbook')" id="subbtn-pos-logbook" class="subtab-btn"> <i class="fa-solid fa-book-journal-whills"></i> 6. Logbook </button> <button onclick="switchSubTab('pos', 'relatorio')" id="subbtn-pos-relatorio" class="subtab-btn"> <i class="fa-solid fa-file-contract"></i> 7. Relatório </button> <button onclick="switchSubTab('pos', 'manuais')" id="subbtn-pos-manuais" class="subtab-btn"> <i class="fa-solid fa-book"></i> 8. Catálogo PN & Manuais </button> </div> </div> <!-- SUB-ABA 1: P.O.s & CLIENTES (VISÃO LIMPA DO PÓS-VENDA) --> <div id="subtab-pos-pos_clientes" class="subtab-pos subtab-anim space-y-4"> <div class="les-card p-5 space-y-4"> <div class="flex flex-wrap justify-between items-center gap-3 border-b pb-3"> <div> <h3 class="text-sm font-bold uppercase text-zinc-900">Pedidos Gerados (P.O.) & Previsão do Cliente</h3> <p class="text-xs text-zinc-500">Acompanhe os pedidos em andamento, dados do cliente e o que será necessário na entrega.</p> </div> <input type="text" id="filtro-pos-po-clientes" oninput="renderPosVendaPOClientes()" placeholder="Buscar P.O., Cliente ou Drone..." class="border rounded-lg px-3 py-1.5 text-xs w-64"> </div> <div id="lista-pos-po-clientes" class="space-y-3"></div> </div> </div> <!-- SUB-ABA 2: DEMANDAS DO CLIENTE (INTEGRADA COM FROTA S.N., LOGBOOK, ESTOQUE E O.S.) --> <div id="subtab-pos-acionamentos" class="subtab-pos subtab-anim hidden space-y-4"> <div class="les-card p-5 space-y-4"> <div class="border-b pb-2 flex justify-between items-center"> <h3 class="text-sm font-bold uppercase text-zinc-900">Registrar Demanda do Cliente (Integrada à Frota S.N., Logbook e O.S.)</h3> </div> <form onsubmit="salvarDemandaPosVenda(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <div> <label class="font-semibold block mb-1">Cliente *</label> <select id="dem-cliente" required class="w-full border rounded-lg px-2.5 py-2 bg-white"></select> </div> <div> <label class="font-semibold block mb-1">S.N. Equipamento *</label> <select id="dem-sn" onchange="aoSelecionarSNDemanda()" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-mono"></select> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Tipo de Intervenção *</label> <select id="dem-tipo-intervencao" onchange="aoMudarTipoIntervencaoDemanda()" required class="w-full border rounded-lg px-2.5 py-2 bg-white font-bold text-zinc-900"> <option value="ACIONAMENTO_EQUIPAMENTO">🚨 Acionamento de Equipamento (+1 Acionamento & Logbook)</option> <option value="MANUTENCAO_PREVENTIVA">🛡️ Manutenção Preventiva (Atualiza Horas & Revisão +12m)</option> <option value="MANUTENCAO_PERIODICA">📅 Manutenção Periódica 12 Meses (Renova Ciclo S.N.)</option> <option value="MANUTENCAO_CORRETIVA">🔧 Manutenção Corretiva / Reparo</option> <option value="INTEGRACAO_INSTALACAO">🧩 Integração / Instalação em Aeronave</option> </select> </div> <div> <label class="font-semibold block mb-1">Horas Atuais (S.N.) *</label> <input type="number" step="0.5" id="dem-horas" required value="0" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Próx. Revisão (+12m)</label> <input type="date" id="dem-nova-revisao" class="w-full border rounded-lg px-2.5 py-2 bg-zinc-50"> </div> <div> <label class="font-semibold block mb-1">Prioridade *</label> <select id="dem-prioridade" class="w-full border rounded-lg px-2.5 py-2 font-semibold"> <option value="Normal">Normal</option> <option value="Alta">Alta</option> <option value="Crítica (AOG)">Crítica (AOG)</option> </select> </div> <div> <label class="font-semibold block mb-1">Prazo *</label> <input type="date" id="dem-prazo" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="md:col-span-2"> <label class="font-semibold block mb-1">Peça do Estoque Utilizada (Opcional)</label> <select id="dem-estoque-item" class="w-full border rounded-lg px-2.5 py-2 bg-white font-mono"> <option value="">-- Sem baixa de estoque --</option> </select> </div> <div> <label class="font-semibold block mb-1">Qtd. Peça</label> <input type="number" min="1" value="1" id="dem-estoque-qtd" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Status *</label> <select id="dem-status" class="w-full border rounded-lg px-2.5 py-2 font-semibold"> <option value="Em Atendimento">Em Atendimento</option> <option value="Em Oficina MRO">Em Oficina MRO</option> <option value="Concluído">Concluído</option> </select> </div> <div class="md:col-span-3"> <label class="font-semibold block mb-1">Descrição da Demanda / Serviço *</label> <input type="text" id="dem-desc" required placeholder="Descreva o atendimento ou procedimento..." class="w-full border rounded-lg px-2.5 py-2"> </div> <div class="flex items-end"> <label class="inline-flex items-center gap-2 cursor-pointer font-bold text-emerald-800 bg-emerald-50 border border-emerald-200 px-3 py-2 rounded-lg w-full"> <input type="checkbox" id="dem-gerar-os-auto" checked class="rounded"> <span>Gerar O.S. Automática</span> </label> </div> <div class="md:col-span-4 flex justify-end pt-1"> <button type="submit" class="bg-zinc-950 hover:bg-zinc-800 text-white rounded-lg font-bold px-5 py-2 shadow"> Salvar Demanda & Integrar </button> </div> </form> <div class="overflow-x-auto pt-2"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"> <tr> <th class="p-2.5">Protocolo</th> <th class="p-2.5">Cliente & S.N.</th> <th class="p-2.5">Tipo</th> <th class="p-2.5">Descrição</th> <th class="p-2.5">Integrações</th> <th class="p-2.5">Status</th> </tr> </thead> <tbody id="tabela-demandas" class="divide-y"></tbody> </table> </div> </div> </div> <!-- SUB-ABA 3: ORDENS DE SERVIÇO (O.S.) DE MANUTENÇÃO DE DRONES (COM EDIÇÃO COMPLETA E DOCUMENTO UNIFICADO ENXUTO) --> <div id="subtab-pos-mro_drones" class="subtab-pos subtab-anim hidden space-y-4"> <div class="les-card p-5 space-y-4" id="card-form-os-mro"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <div> <span id="mro-mode-badge" class="text-[10px] uppercase tracking-widest text-sky-600 font-bold block">Manutenção de Drones & Sistemas (MRO)</span> <h3 id="mro-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Nova Ordem de Serviço (O.S.) / Editar O.S.</h3> </div> <button type="button" id="btn-cancelar-edicao-mro" onclick="cancelarEdicaoOSMRO()" class="hidden px-3 py-1.5 bg-amber-100 hover:bg-amber-200 text-amber-900 rounded-lg text-xs font-bold"> Cancelar Edição da O.S. </button> </div> <form id="form-os-mro" onsubmit="salvarOrdemServicoMRO(event)" class="space-y-3 text-xs"> <input type="hidden" id="mro-edit-id" value=""> <div class="grid grid-cols-1 md:grid-cols-4 gap-3"> <div> <label class="font-semibold block mb-1">Nº da O.S. *</label> <input type="text" id="mro-numero-os" required placeholder="Ex: OS-2026-001" class="w-full border rounded-lg px-2.5 py-2 font-mono font-bold"> </div> <div> <label class="font-semibold block mb-1">Data *</label> <input type="date" id="mro-abertura" required class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Cliente *</label> <select id="mro-cliente" required class="w-full border rounded-lg px-2.5 py-2 bg-white"></select> </div> <div> <label class="font-semibold block mb-1">Status da O.S. *</label> <select id="mro-status-fluxo" class="w-full border rounded-lg px-2.5 py-2 font-bold bg-white"> <option value="1. OS Aberta / Recebida">1. OS Aberta / Recebida</option> <option value="2. Em Diagnóstico e Execução">2. Em Diagnóstico e Execução</option> <option value="3. Em Testes Finais">3. Em Testes Finais</option> <option value="4. Concluída / Expedida">4. Concluída / Expedida</option> </select> </div> <div> <label class="font-semibold block mb-1">Modelo do Drone *</label> <input type="text" id="mro-modelo" required placeholder="Ex: DJI Matrice 350 RTK" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">S/N do Drone *</label> <input type="text" id="mro-sn-eq" required placeholder="Ex: ANV-M350-4410" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Sistema / P/N</label> <input type="text" id="mro-sistema" value="DRS-M350-PRO" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">S.N. do Sistema</label> <input type="text" id="mro-sn-sistema" placeholder="Ex: SN-2025-0101" class="w-full border rounded-lg px-2.5 py-2 font-mono"> </div> <div> <label class="font-semibold block mb-1">Tipo de Serviço *</label> <select id="mro-tipo-atendimento" class="w-full border rounded-lg px-2.5 py-2 font-semibold bg-white"> <option value="Preventiva">Preventiva</option> <option value="Corretiva">Corretiva</option> <option value="Integração / instalação">Integração / Instalação</option> <option value="Garantia">Garantia</option> </select> </div> <div> <label class="font-semibold block mb-1">Prioridade</label> <select id="mro-prioridade" class="w-full border rounded-lg px-2.5 py-2 bg-white"> <option value="Normal">Normal</option> <option value="Alta">Alta</option> <option value="Crítica">Crítica (AOG)</option> </select> </div> <div> <label class="font-semibold block mb-1">Responsável Técnico *</label> <input type="text" id="mro-tecnico" required value="Gabriel Bezerra" class="w-full border rounded-lg px-2.5 py-2"> </div> <div> <label class="font-semibold block mb-1">Garantia / Ref.</label> <input type="text" id="mro-proposta-ref" placeholder="Ex: Garantia 90d / Contrato" class="w-full border rounded-lg px-2.5 py-2"> </div> </div> <!-- Campos ocultos auxiliares p/ compatibilidade --> <input type="hidden" id="mro-unidade" value="Oficina Central LES • Anápolis-GO"> <input type="hidden" id="mro-equipamento" value="Aeronave RPAS / Drone"> <input type="hidden" id="mro-garantia" value="Conforme Ordem de Serviço"> <input type="hidden" id="mro-coordenador" value="Matheus Azevedo"> <input type="hidden" id="mro-acessorios" value="Case rígido, aeronave, baterias, rádio controle e cabos"> <div class="grid grid-cols-1 md:grid-cols-2 gap-3 bg-zinc-50 p-3.5 rounded-xl border"> <div> <label class="font-semibold block mb-1">1. Solicitação do Cliente / Problema Relatado *</label> <input type="text" id="mro-problema" required placeholder="Descreva o motivo da manutenção..." class="w-full border rounded-lg px-2.5 py-1.5 bg-white"> </div> <div> <label class="font-semibold block mb-1">2. Diagnóstico Técnico *</label> <input type="text" id="mro-diagnostico" required value="Inspeção estrutural e eletrônica realizada conforme parâmetros do fabricante." class="w-full border rounded-lg px-2.5 py-1.5 bg-white"> </div> <div> <label class="font-semibold block mb-1">3. Serviços Executados & Peças Aplicadas *</label> <input type="text" id="mro-atividades" required value="Revisão geral, limpeza técnica, verificação de conectores, calibração IMU e testes." class="w-full border rounded-lg px-2.5 py-1.5 bg-white"> </div> <div> <label class="font-semibold block mb-1">4. Resultado dos Testes Finais & Liberação *</label> <input type="text" id="mro-testes-expedicao" required value="100% Aprovado nos testes de bancada • Liberado para operação." class="w-full border rounded-lg px-2.5 py-1.5 bg-white"> </div> <input type="hidden" id="mro-pecas-horas" value="Conforme escopo da O.S."> </div> <div class="flex justify-end"> <button type="submit" id="btn-submit-mro" class="bg-zinc-950 hover:bg-zinc-800 text-white px-6 py-2 rounded-lg font-bold shadow"> <i class="fa-solid fa-floppy-disk mr-1.5"></i> Salvar Ordem de Serviço (O.S.) </button> </div> </form> </div> <!-- Lista Enxuta de Ordens de Serviço com Botão de Editar O.S. e PDF Único Consolidado --> <div class="les-card p-5 space-y-3"> <h4 class="text-xs font-bold uppercase text-zinc-900 border-b pb-2">Ordens de Serviço Cadastradas (Clique em "Editar O.S." para alterar ou "PDF da O.S." para imprimir)</h4> <div id="lista-os-mro-cards" class="space-y-3"></div> </div> </div> <!-- SUB-ABA 4: FROTA S.N. --> <div id="subtab-pos-frota" class="subtab-pos subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4" id="card-form-sn"> <div class="flex justify-between items-center border-b pb-2"> <h3 id="sn-form-titulo" class="text-sm font-bold uppercase text-zinc-900">Cadastro / Edição Técnica de S.N. (Revisão Programada: +12m da 1ª Instalação)</h3> <button type="button" id="btn-cancelar-edicao-sn" onclick="cancelarEdicaoSN()" class="hidden px-3 py-1 bg-amber-100 text-amber-900 rounded-lg text-xs font-bold">Cancelar Edição</button> </div> <form id="form-sn" onsubmit="salvarSN(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <input type="hidden" id="sn-edit-original" value=""> <div><label class="font-semibold block mb-1">S.N. do Equipamento *</label><input type="text" id="sn-numero" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Part Number (PN) *</label><select id="sn-pn" onchange="calcularDatasAutomaticasSN()" required class="w-full border rounded-lg px-2.5 py-2"></select></div> <div><label class="font-semibold block mb-1">Cliente *</label><select id="sn-cliente" required class="w-full border rounded-lg px-2.5 py-2"></select></div> <div><label class="font-semibold block mb-1">SN ANV (Aeronave) *</label><input type="text" id="sn-anv" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Data 1ª Instalação *</label><input type="date" id="sn-instalacao" onchange="calcularDatasAutomaticasSN()" required class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Shelf Life *</label><input type="date" id="sn-shelflife" required class="w-full border rounded-lg px-2.5 py-2 bg-zinc-50"></div> <div><label class="font-semibold block mb-1">Horas de Voo *</label><input type="number" step="0.5" id="sn-horas" required value="0" class="w-full border rounded-lg px-2.5 py-2"></div> <div><label class="font-semibold block mb-1">Revisão (12 Meses) *</label><input type="date" id="sn-revisao" required class="w-full border rounded-lg px-2.5 py-2 bg-zinc-50"></div> <div class="md:col-span-4 flex justify-end"><button type="submit" id="btn-submit-sn" class="bg-zinc-950 text-white px-6 py-2 rounded-lg font-semibold">Registrar S.N.</button></div> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"><tr><th class="p-3">S.N. (Abrir Ficha)</th><th class="p-3">PN</th><th class="p-3">Cliente</th><th class="p-3">SN ANV</th><th class="p-3">1ª Inst.</th><th class="p-3">Horas / Acionamentos</th><th class="p-3">Shelf / Revisão</th><th class="p-3">Rastreabilidade</th><th class="p-3 text-right">Exclusão</th></tr></thead> <tbody id="tabela-sns" class="divide-y"></tbody> </table> </div> </div> <div id="subtab-pos-sndetalhe" class="subtab-pos subtab-anim hidden space-y-6"> <button onclick="switchSubTab('pos', 'frota')" class="bg-zinc-200 hover:bg-zinc-300 text-zinc-900 px-4 py-2 rounded-lg text-xs font-semibold"><i class="fa-solid fa-arrow-left mr-1"></i> Voltar para Lista de S.N.</button> <div id="ativo-detalhe-conteudo" class="space-y-6"></div> </div> <!-- SUB-ABA 5: INTEGRAÇÃO --> <div id="subtab-pos-integracao" class="subtab-pos subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4"> <form onsubmit="salvarIntegracao(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <select id="int-sn" required class="border rounded-lg px-2.5 py-2"></select> <input type="text" id="int-resp" required placeholder="Equipe Envolvida *" class="border rounded-lg px-2.5 py-2"> <input type="date" id="int-data" required class="border rounded-lg px-2.5 py-2"> <select id="int-status" class="border rounded-lg px-2.5 py-2 font-semibold"><option value="Aguardando integração">Aguardando integração</option><option value="Em integração">Em integração</option><option value="Integrado">Integrado</option></select> <input type="text" id="int-testes" required placeholder="Checklist e Testes Realizados *" class="border rounded-lg px-2.5 py-2 md:col-span-3"> <button type="submit" class="bg-zinc-950 text-white rounded-lg font-semibold py-2">Salvar Integração</button> </form> <table class="w-full text-left text-xs"><tbody id="tabela-integracoes" class="divide-y"></tbody></table> </div> </div> <!-- SUB-ABA 6: LOGBOOK --> <div id="subtab-pos-logbook" class="subtab-pos subtab-anim hidden space-y-6"> <div class="les-card p-6 space-y-4"> <form onsubmit="salvarLogbook(event)" class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs"> <select id="log-sn" required class="border rounded-lg px-2.5 py-2"></select> <select id="log-tipo" required class="border rounded-lg px-2.5 py-2"> <option value="Revisão Programada (12 Meses)">Revisão Programada (12 Meses)</option> <option value="Manutenção Preventiva">Manutenção Preventiva</option> <option value="Acionamento">Acionamento</option> <option value="Manutenção Corretiva">Manutenção Corretiva</option> </select> <select id="log-origem-horas" class="border rounded-lg px-2.5 py-2"><option value="Verificada Documentalmente (Oficina)">Verificada Documentalmente (Oficina)</option><option value="Importada de Log / Telemetria">Importada de Log / Telemetria</option><option value="Informada pelo Cliente">Informada pelo Cliente</option></select> <input type="date" id="log-data" required class="border rounded-lg px-2.5 py-2"> <input type="number" step="0.5" id="log-horas" required placeholder="Horas *" class="border rounded-lg px-2.5 py-2"> <input type="text" id="log-desc" required placeholder="Procedimentos Executados *" class="border rounded-lg px-2.5 py-2 md:col-span-2"> <button type="submit" class="bg-zinc-950 text-white rounded-lg font-semibold py-2">Registrar Logbook</button> </form> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"><tr><th class="p-3">Data</th><th class="p-3">S.N.</th><th class="p-3">Evento</th><th class="p-3">Horas</th><th class="p-3">Descrição</th><th class="p-3">Rastreabilidade</th><th class="p-3 text-right">Exclusão</th></tr></thead> <tbody id="tabela-logbook" class="divide-y"></tbody> </table> </div> </div> <!-- SUB-ABA 7: RELATÓRIO MENSAL --> <div id="subtab-pos-relatorio" class="subtab-pos subtab-anim hidden space-y-6"> <div class="flex justify-between items-center no-print"> <h3 class="text-sm font-bold uppercase">Relatório Mensal de Serviços MRO</h3> <div class="flex gap-2"><input type="month" id="filtro-mes" onchange="renderRelatorioMensal()" class="border rounded-lg px-3 py-1.5 text-xs bg-white"><button onclick="window.print()" class="bg-zinc-950 text-white px-4 py-1.5 rounded-lg text-xs font-semibold">Imprimir / PDF</button></div> </div> <div class="les-card p-8 space-y-6"> <div class="border-b-2 border-zinc-950 pb-4 flex justify-between items-center"> <div class="flex items-center gap-4"> <svg viewBox="0 0 360 155" class="w-28 h-auto shrink-0" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M22 18 V102 H98" stroke="#0A0A0A" stroke-width="15" stroke-linecap="square" stroke-linejoin="miter"/> <line x1="128" y1="25.5" x2="218" y2="25.5" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="60" x2="218" y2="60" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="94.5" x2="218" y2="94.5" stroke="#0A0A0A" stroke-width="15"/> <path d="M332 25.5 H275 C255 25.5 246 35 246 43.5 C246 54 256 60 274 60 H308 C328 60 338 68 338 78.5 C338 89 327 94.5 306 94.5 H244" stroke="#0A0A0A" stroke-width="15"/> <text x="180" y="142" text-anchor="middle" fill="#0A0A0A" font-family="Inter, sans-serif" font-size="23" font-weight="500" letter-spacing="14">AVIATION</text> </svg> <div><h4 class="text-base font-bold uppercase">LES AVIATION • Consolidado Mensal MRO</h4><p id="relatorio-subtitulo" class="text-xs text-zinc-500"></p></div> </div> <span id="relatorio-total" class="text-2xl font-extrabold">0</span> </div> <table class="w-full text-left text-xs"><tbody id="tabela-relatorio-mensal" class="divide-y"></tbody></table> </div> </div> <!-- SUB-ABA 8: BASE PN & MANUAIS (COM VISUALIZADOR INTEGRADO E CONVERSÃO BLOB URL) --> <div id="subtab-pos-manuais" class="subtab-pos subtab-anim hidden space-y-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="les-card p-5 space-y-3"> <h3 class="text-xs font-bold uppercase">Base de Part Numbers (PN) — Integrada às Cotações & Estoque</h3> <form onsubmit="salvarPN(event)" class="grid grid-cols-3 gap-2 text-xs"> <input type="text" id="pn-codigo" required placeholder="PN (Ex: DRS-M350-PRO) *" class="border rounded-lg px-2 py-1.5"> <input type="text" id="pn-desc" required placeholder="Nome / Descrição do Produto *" class="border rounded-lg px-2 py-1.5"> <input type="number" id="pn-shelf" required value="24" placeholder="Shelf (Meses)" class="border rounded-lg px-2 py-1.5"> <button type="submit" class="col-span-3 bg-zinc-950 text-white py-1.5 rounded-lg font-semibold">Adicionar PN ao Catálogo</button> </form> <table class="w-full text-left text-xs"><tbody id="tabela-pns" class="divide-y"></tbody></table> </div> <div class="les-card p-5 space-y-3"> <h3 class="text-xs font-bold uppercase">Controle de Versões dos Manuais Técnicos (PDF Homologável)</h3> <form onsubmit="salvarManual(event)" class="grid grid-cols-2 gap-2 text-xs"> <select id="man-pn" required class="border rounded-lg px-2 py-1.5"></select> <input type="text" id="man-versao" required placeholder="Versão (Rev 3.2)" class="border rounded-lg px-2 py-1.5"> <input type="text" id="man-titulo" required placeholder="Título do Manual" class="border rounded-lg px-2 py-1.5 col-span-2"> <input type="date" id="man-data" required class="border rounded-lg px-2 py-1.5"> <input type="file" id="man-pdf" accept=".pdf,image/*" class="text-[11px]"> <input type="text" id="man-obs" required placeholder="Notas de Revisão / Especificações Técnicas" class="border rounded-lg px-2 py-1.5 col-span-2"> <button type="submit" class="col-span-2 bg-zinc-950 text-white py-1.5 rounded-lg font-semibold">Cadastrar Versão do Manual</button> </form> </div> </div> <div class="les-card overflow-x-auto"> <table class="w-full text-left text-xs"> <thead class="bg-zinc-950 text-white uppercase"><tr><th class="p-3">PN</th><th class="p-3">Manual</th><th class="p-3">Versão</th><th class="p-3">Visualizar / Baixar Manual</th><th class="p-3">Rastreabilidade</th><th class="p-3">Status Diretoria</th><th class="p-3 text-right">Homologação</th></tr></thead> <tbody id="tabela-manuais" class="divide-y"></tbody> </table> </div> </div> </section> </div> </main> </div> <!-- MOTOR V1.23: PROTEÇÃO ANTI-PERDA DE DADOS NA NUVEM/LOCALSTORAGE + INDEXEDDB + COMISSÃO 4% DO VENDEDOR + VISUALIZADOR DE MANUAIS --> <script> const VERSAO_SISTEMA = '1.24'; const TAXA_COMISSAO_VENDEDOR = 0.04; // 4% de comissão do vendedor sobre a conta gerada na proposta const STORAGE_KEY = 'les_aviation_erp_prod_v1_20'; const LAST_NON_EMPTY_BACKUP_KEY = 'les_aviation_erp_snapshot_seguro_v1'; const PRODUCTION_RESET_EPOCH = 202610212010; const LOGO_STORAGE_KEY = 'les_aviation_custom_logo'; const SIDEBAR_COLLAPSED_KEY = 'les_aviation_sidebar_collapsed'; const URL_GOOGLE_APPS_SCRIPT = "https://script.google.com/macros/s/AKfycbzDYOIMfSvqfkL_mZqTWd6z9MJHniPi36qJQnKQuIMRaKzCoY7vdX3hT0yJ1Ru3zpuM/exec"; // ========================================================================= // COFRE DE ARQUIVOS GRANDES EM INDEXEDDB (EVITA ESTOURAR OS 5MB DO LOCALSTORAGE) // ========================================================================= const IDB_NAME = 'les_aviation_idb_arquivos_v1'; const IDB_STORE = 'arquivos_binarios'; let cacheArquivosMemoria = {}; function abrirIndexedDBArquivos() { return new Promise((resolve) => { try { if (!window.indexedDB) { resolve(null); return; } const req = window.indexedDB.open(IDB_NAME, 1); req.onupgradeneeded = (ev) => { const idb = ev.target.result; if (!idb.objectStoreNames.contains(IDB_STORE)) { idb.createObjectStore(IDB_STORE); } }; req.onsuccess = (ev) => resolve(ev.target.result); req.onerror = () => resolve(null); } catch (e) { resolve(null); } }); } async function salvarArquivoNoIndexedDB(chave, conteudoBase64) { if (!chave || !conteudoBase64) return; cacheArquivosMemoria[chave] = conteudoBase64; try { const idb = await abrirIndexedDBArquivos(); if (!idb) return; const tx = idb.transaction(IDB_STORE, 'readwrite'); tx.objectStore(IDB_STORE).put(conteudoBase64, chave); } catch (e) {} } async function restaurarArquivosDoIndexedDB() { try { const idb = await abrirIndexedDBArquivos(); if (!idb) return; const tx = idb.transaction(IDB_STORE, 'readonly'); const store = tx.objectStore(IDB_STORE); const req = store.openCursor(); req.onsuccess = (ev) => { const cursor = ev.target.result; if (cursor) { cacheArquivosMemoria[cursor.key] = cursor.value; cursor.continue(); } else { // Re-hidrata PDFs de manuais e dossiês que estavam salvos no IndexedDB let reidratou = false; (db.manuais || []).forEach(m => { const ch = `MAN_PDF_${m.id}`; if ((!m.pdf || m.pdf === 'IDB_STORED') && cacheArquivosMemoria[ch]) { m.pdf = cacheArquivosMemoria[ch]; reidratou = true; } }); (db.desenvolvimento?.tarefas || []).forEach(t => { (t.subtarefas || []).forEach(s => { (s.anexos || []).forEach(anx => { const chSub = `SUBDEV_DOC_${s.id}_${anx.id}`; if ((!anx.url || anx.url === 'IDB_STORED') && cacheArquivosMemoria[chSub]) { anx.url = cacheArquivosMemoria[chSub]; reidratou = true; } }); }); }); if (reidratou && sessaoAtual.perfil) renderAll(); } }; } catch (e) {} } const USUARIOS_AUTORIZADOS = [ // CONSELHO & CEO — LES AVIATION HOLDING (Acesso Global à Holding e às Subsidiárias) { nome: 'Matheus Azevedo', senhaPadrao: 'FloquinhaLuna', perfilPrincipal: 'diretoria', cargo: 'CEO — LES Aviation Holding & Conselho', escopoSubsidiaria: 'HOLDING', abaInicial: 'diretoria', areasPermitidas: ['diretoria', 'diretor_drones', 'diretor_defense', 'defense', 'vendedor', 'logistica', 'pos_venda'], acessoTotal: true, ehMaster: true }, { nome: 'Flavio', senhaPadrao: 'FLlog', perfilPrincipal: 'diretoria', cargo: 'Conselho — LES Aviation Holding', escopoSubsidiaria: 'HOLDING', abaInicial: 'diretoria', areasPermitidas: ['diretoria', 'diretor_drones', 'diretor_defense', 'defense', 'vendedor', 'logistica', 'pos_venda'], acessoTotal: true, ehMaster: false }, { nome: 'Wesley', senhaPadrao: 'WElog', perfilPrincipal: 'diretoria', cargo: 'Conselho — LES Aviation Holding', escopoSubsidiaria: 'HOLDING', abaInicial: 'diretoria', areasPermitidas: ['diretoria', 'diretor_drones', 'diretor_defense', 'defense', 'vendedor', 'logistica', 'pos_venda'], acessoTotal: true, ehMaster: false }, // SUBSIDIÁRIA 1: LES DRONES (Acesso estrito à operação LES Drones — Sem acesso à LES Defense) { nome: 'Caio Cesar', senhaPadrao: 'CCvendas', perfilPrincipal: 'diretor_drones', cargo: 'Diretor & Comercial — LES Drones', escopoSubsidiaria: 'DRONES', abaInicial: 'vendas', areasPermitidas: ['diretor_drones', 'vendedor', 'pos_venda'], acessoTotal: false, ehMaster: false }, { nome: 'Gabriel Bezerra', senhaPadrao: 'GBmanut', perfilPrincipal: 'pos_venda', cargo: 'Head Manutenção — LES Drones', escopoSubsidiaria: 'DRONES', abaInicial: 'posvenda', areasPermitidas: ['pos_venda', 'vendedor'], acessoTotal: false, ehMaster: false }, { nome: 'Ramon Dias', senhaPadrao: 'RDmanut', perfilPrincipal: 'pos_venda', cargo: 'Manutenção e MRO — LES Drones', escopoSubsidiaria: 'DRONES', abaInicial: 'posvenda', areasPermitidas: ['pos_venda', 'vendedor'], acessoTotal: false, ehMaster: false }, { nome: 'Lucas Azevedo', senhaPadrao: 'LAvendas', perfilPrincipal: 'vendedor', cargo: 'Vendas / Comercial — LES Drones', escopoSubsidiaria: 'DRONES', abaInicial: 'vendas', areasPermitidas: ['vendedor'], acessoTotal: false, ehMaster: false }, // SUBSIDIÁRIA 2: LES DEFENSE (Acesso estrito à operação LES Defense — Sem acesso à LES Drones) { nome: 'Lucas Pires', senhaPadrao: 'LPdefesa', perfilPrincipal: 'diretor_defense', cargo: 'Diretor & Comercial — LES Defense', escopoSubsidiaria: 'DEFENSE', abaInicial: 'defense', areasPermitidas: ['diretor_defense', 'defense'], acessoTotal: false, ehMaster: false }, { nome: 'Jean Natividade', senhaPadrao: 'JNdefesa', perfilPrincipal: 'defense_tecnico', cargo: 'Análise de Ameaças, Operações & Vendas — LES Defense', escopoSubsidiaria: 'DEFENSE', abaInicial: 'defense', areasPermitidas: ['defense_tecnico', 'defense'], acessoTotal: false, ehMaster: false }, // HEAD LOGÍSTICA — OPERAÇÃO INTEGRADA { nome: 'Angelica', senhaPadrao: 'Anlog', perfilPrincipal: 'logistica', cargo: 'Head Logística — Operação Integrada', escopoSubsidiaria: 'LOGISTICA', abaInicial: 'logistica', areasPermitidas: ['logistica', 'vendedor'], acessoTotal: false, ehMaster: false } ]; const ETAPAS_TRACKING_DEFINICAO = [ { codigo: '1_PEDIDO_RECEBIDO', ordem: 1, curto: '1. Pedido Recebido', label: '1. Pedido Recebido / Em Validação de Dossiê', cor: 'bg-zinc-600' }, { codigo: '2_PO_EMITIDO', ordem: 2, curto: '2. P.O. Emitido', label: '2. P.O. Emitido ao Fabricante Internacional', cor: 'bg-indigo-600' }, { codigo: '3_EM_PRODUCAO', ordem: 3, curto: '3. Em Produção', label: '3. Em Produção / Separação no Fabricante', cor: 'bg-violet-600' }, { codigo: '4_DESPACHADO_ORIGEM', ordem: 4, curto: '4. Despachado', label: '4. Já Despachado da Fábrica / Pronto p/ Embarque', cor: 'bg-amber-600' }, { codigo: '5_TRANSITO_INTERNACIONAL', ordem: 5, curto: '5. Trânsito Int.', label: '5. Em Trânsito Internacional (Aéreo / Marítimo)', cor: 'bg-sky-600' }, { codigo: '6_CHEGADA_BRASIL', ordem: 6, curto: '6. No Brasil', label: '6. Chegada no Brasil / Recinto Alfandegado', cor: 'bg-cyan-600' }, { codigo: '7_EM_DESEMBARACO', ordem: 7, curto: '7. Em Desembaraço', label: '7. Em Processo de Desembaraço Aduaneiro (RFB / ANAC)', cor: 'bg-orange-600' }, { codigo: '8_DESEMBARACADO', ordem: 8, curto: '8. Desembaraçado', label: '8. Já Desembaraçado / Nacionalizado e Liberado', cor: 'bg-teal-600' }, { codigo: '9_TRANSPORTE_NACIONAL', ordem: 9, curto: '9. Transp. Nacional', label: '9. Em Transporte Nacional (Rumo à Base LES / Cliente)', cor: 'bg-blue-600' }, { codigo: '10_ENTREGUE', ordem: 10, curto: '10. Entregue', label: '10. Entregue ao Cliente / Pedido Finalizado', cor: 'bg-emerald-600' } ]; let sessaoAtual = { perfil: null, areasPermitidas: [], nome: null, cargo: null, acessoTotal: false, ehMaster: false }; let snSelecionadoAtual = null; let poSelecionadoModal = null; let acaoCancelamentoPendente = null; let sincronizandoAgora = false; let ultimoSaveLocal = 0; let chartStatusInst = null; let chartValoresInst = null; let blobUrlManualAtivo = null; const PERMISSOES_MODULOS = { diretoria: ['diretoria', 'financeiro', 'vendas', 'defense', 'desenvolvimento', 'logistica', 'estoque', 'posvenda'], diretor_drones: ['vendas', 'financeiro', 'estoque', 'posvenda', 'logistica', 'desenvolvimento'], diretor_defense: ['defense', 'financeiro', 'desenvolvimento'], defense: ['defense', 'desenvolvimento'], defense_tecnico: ['defense', 'desenvolvimento'], vendedor: ['vendas', 'estoque'], logistica: ['logistica', 'estoque', 'vendas'], pos_venda: ['posvenda', 'estoque', 'vendas', 'desenvolvimento'] }; const MENU_DEFINICAO = [ { id: 'diretoria', icon: 'fa-chart-pie', label: 'Dashboard' }, { id: 'financeiro', icon: 'fa-sack-dollar', label: 'Financeiro' }, { id: 'vendas', icon: 'fa-file-invoice-dollar', label: 'Vendas' }, { id: 'defense', icon: 'fa-shield-halved', label: 'LES Defense' }, { id: 'desenvolvimento', icon: 'fa-list-check', label: 'Desenvolvimento' }, { id: 'logistica', icon: 'fa-plane-arrival', label: 'Logística' }, { id: 'estoque', icon: 'fa-boxes-stacked', label: 'Estoque' }, { id: 'posvenda', icon: 'fa-screwdriver-wrench', label: 'Pós-venda e MRO' } ]; // ========================================================= // CONVERSOR DE DATA-URL BASE64 PARA BLOB URL (EVITA BLOQUEIO DO CHROME/SAFARI EM MANUAIS PDF) // ========================================================= function converterDataUrlParaBlobUrl(dataUrl) { if (!dataUrl || typeof dataUrl !== 'string') return null; if (!dataUrl.startsWith('data:')) return dataUrl; try { const partes = dataUrl.split(','); if (partes.length < 2) return null; const cabecalho = partes[0]; const base64Str = partes[1]; const mimeMatch = cabecalho.match(/data:([^;]+);/); const mimeType = mimeMatch ? mimeMatch[1] : 'application/pdf'; const byteChars = atob(base64Str); const byteNumbers = new Array(byteChars.length); for (let i = 0; i < byteChars.length; i++) { byteNumbers[i] = byteChars.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: mimeType }); return URL.createObjectURL(blob); } catch (err) { console.error('Erro ao converter DataURL em BlobURL:', err); return null; } } function toggleSidebarCollapse() { const sb = document.getElementById('main-sidebar'); const icon = document.getElementById('sidebar-toggle-icon'); if (!sb) return; sb.classList.toggle('sidebar-collapsed'); const isCollapsed = sb.classList.contains('sidebar-collapsed'); if (icon) { icon.className = isCollapsed ? 'fa-solid fa-angles-right text-xs' : 'fa-solid fa-angles-left text-xs'; } try { localStorage.setItem(SIDEBAR_COLLAPSED_KEY, isCollapsed ? '1' : '0'); } catch (e) {} } function restaurarEstadoSidebar() { try { if (localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === '1') { const sb = document.getElementById('main-sidebar'); const icon = document.getElementById('sidebar-toggle-icon'); if (sb) sb.classList.add('sidebar-collapsed'); if (icon) icon.className = 'fa-solid fa-angles-right text-xs'; } } catch (e) {} } function atualizarIndicadorNuvem(estado, texto) { const dot = document.getElementById('cloud-status-dot'); const label = document.getElementById('cloud-status-label'); const lDot = document.getElementById('login-cloud-dot'); const lText = document.getElementById('login-cloud-text'); const cores = { ok: 'bg-emerald-500', sync: 'bg-amber-400 animate-pulse', erro: 'bg-rose-500' }; if (dot && label) { dot.className = 'w-2.5 h-2.5 rounded-full ' + (cores[estado] || cores.ok); label.textContent = texto; } if (lDot && lText) { lDot.className = 'w-2 h-2 rounded-full ' + (cores[estado] || cores.ok); lText.textContent = texto; } } function usuarioEhMasterMatheus() { return sessaoAtual.ehMaster === true || normalizarTexto(sessaoAtual.nome) === 'matheus azevedo'; } function obterModulosLiberadosSessao() { if (!sessaoAtual.perfil) return []; if (sessaoAtual.acessoTotal || usuarioEhMasterMatheus()) { return ['diretoria', 'financeiro', 'vendas', 'defense', 'desenvolvimento', 'logistica', 'estoque', 'posvenda']; } const modulosSet = new Set(); (sessaoAtual.areasPermitidas || [sessaoAtual.perfil]).forEach(area => { (PERMISSOES_MODULOS[area] || []).forEach(m => modulosSet.add(m)); }); return Array.from(modulosSet); } function usuarioTemArea(area) { if (!sessaoAtual.perfil) return false; if (sessaoAtual.acessoTotal || usuarioEhMasterMatheus()) return true; return (sessaoAtual.areasPermitidas || []).includes(area) || sessaoAtual.perfil === area; } function usuarioEhDiretoriaHolding() { return sessaoAtual.escopoSubsidiaria === 'HOLDING' || usuarioTemArea('diretoria'); } function usuarioEhExclusivoDefense() { return !usuarioEhDiretoriaHolding() && (sessaoAtual.escopoSubsidiaria === 'DEFENSE' || usuarioTemArea('diretor_defense') || usuarioTemArea('defense_tecnico') || usuarioTemArea('defense')); } function usuarioEhExclusivoDrones() { return !usuarioEhDiretoriaHolding() && !usuarioEhExclusivoDefense(); } function usuarioEhDiretorDrones() { return usuarioTemArea('diretor_drones') || usuarioEhDiretoriaHolding(); } function usuarioEhDiretorDefense() { return usuarioTemArea('diretor_defense') || usuarioEhDiretoriaHolding(); } function usuarioTemVisaoFinanceira() { return usuarioEhDiretoriaHolding() || usuarioEhDiretorDrones() || usuarioEhDiretorDefense(); } function usuarioEhDiretoria() { return usuarioTemArea('diretoria'); } function usuarioPodeInserirDossieVendas() { return usuarioTemArea('vendedor') || usuarioTemArea('diretor_drones') || usuarioTemArea('diretoria'); } function usuarioPodeAtualizarLogistica() { return usuarioTemArea('logistica'); } function exigirPermissao(perfisAutorizados, acaoDescricao) { if (!sessaoAtual.perfil) return false; if (sessaoAtual.acessoTotal || usuarioEhMasterMatheus()) return true; const tem = perfisAutorizados.some(p => usuarioTemArea(p)); if (!tem) { registrarAuditoria('SEGURANCA', `Acesso bloqueado: ${acaoDescricao}`, 'RBAC-DENY'); alert(`ACESSO NEGADO: O login de "${sessaoAtual.nome}" não possui permissão para: ${acaoDescricao}.`); return false; } return true; } function normalizarTexto(str) { return (str || '').trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); } function carimboHoraAtual() { const agora = new Date(); const pad = n => String(n).padStart(2, '0'); return `${agora.getFullYear()}-${pad(agora.getMonth()+1)}-${pad(agora.getDate())} ${pad(agora.getHours())}:${pad(agora.getMinutes())}:${pad(agora.getSeconds())}`; } function etiquetaRastreabilidade(obj) { const autor = obj.criadoPor || obj.responsavel || 'Sistema'; const dh = obj.criadoEm || obj.data || ''; const aprov = obj.aprovadoPor ? `<span class="block text-[10px] text-emerald-700 font-semibold"><i class="fa-solid fa-check-circle mr-1"></i>Aprovado por: ${obj.aprovadoPor} (${obj.aprovadoEm || ''})</span>` : ''; const canc = obj.canceladoPor ? `<span class="block text-[10px] text-rose-700 font-semibold"><i class="fa-solid fa-ban mr-1"></i>${obj.status === 'RECUSADA' ? 'Recusado' : 'Cancelado'} por: ${obj.canceladoPor} (${obj.canceladoEm || ''}) ${obj.motivoCancelamento ? `— Motivo: ${obj.motivoCancelamento}` : ''}</span>` : ''; const mod = obj.modificadoPor ? `<span class="block text-[10px] text-sky-700 font-medium"><i class="fa-solid fa-pen mr-1"></i>Editado por: ${obj.modificadoPor} (${obj.modificadoEm})</span>` : ''; return `<span class="block text-[10px] text-zinc-500 font-normal mt-0.5"><i class="fa-solid fa-user-pen mr-1"></i>Gerado por: <b>${autor}</b> ${dh ? `(${dh})` : ''}</span>${mod}${aprov}${canc}`; } // IMPORTANTE: updatedAt em initialDB DEVE SER 0 (e nunca Date.now()), para que uma aba vazia ou o preview // do editor do Squarespace JAMAIS tenha timestamp maior que a nuvem e sobrescreva pedidos cadastrados! const initialDB = { updatedAt: 0, productionEpoch: PRODUCTION_RESET_EPOCH, registrosExcluidos: [], clientes: [], oportunidades: [], anamneses: [], propostas: [], vendas: [], pos: [], fornecedores: [], pns: [ { pn: 'DRS-M350-PRO', desc: 'Sistema de Recuperação por Paraquedas p/ DJI M350', shelf: 24, revMeses: 12, criadoPor: 'Catálogo Oficial LES', criadoEm: '' }, { pn: 'DRS-M30-PRO', desc: 'Sistema de Recuperação por Paraquedas p/ DJI Matrice 30', shelf: 24, revMeses: 12, criadoPor: 'Catálogo Oficial LES', criadoEm: '' }, { pn: 'DRS-M3E-PRO', desc: 'Sistema de Recuperação por Paraquedas p/ DJI Mavic 3 Enterprise', shelf: 24, revMeses: 12, criadoPor: 'Catálogo Oficial LES', criadoEm: '' } ], estoque: [], movimentacoesEstoque: [], sns: [], integracoes: [], logbook: [], demandasPosVenda: [], manuais: [], financeiro: { periodoFechado: '', orcamento: { metaReceita: 0, limiteDespesa: 0 }, planoContas: [ { codigo: '1.1.01', nome: 'Caixa e Bancos', grupo: 'ATIVO' }, { codigo: '1.1.02', nome: 'Contas a Receber de Clientes', grupo: 'ATIVO' }, { codigo: '1.1.03', nome: 'Estoques de Produtos e Peças Aeronáuticas', grupo: 'ATIVO' }, { codigo: '2.1.01', nome: 'Fornecedores Internacionais / P.O.', grupo: 'PASSIVO' }, { codigo: '2.1.02', nome: 'Obrigações de Importação e Frete', grupo: 'PASSIVO' }, { codigo: '3.1.01', nome: 'Capital Social', grupo: 'PL' }, { codigo: '4.1.01', nome: 'Receita de Venda de Produtos Aeronáuticos', grupo: 'RECEITA' }, { codigo: '5.1.01', nome: 'CPV / CMV e Importação', grupo: 'CUSTO' }, { codigo: '6.1.01', nome: 'Despesas Operacionais, Comissões (4%), Ajustes e Fretes', grupo: 'DESPESA' } ], lancamentos: [] }, ordensServicoMRO: [], senhasUsuarios: {}, comunicadosMatheus: [], defense: { clientes: [], consultorias: [], contratos: [], estoque: [], movimentacoesEstoque: [], financeiro: { lancamentos: [] } }, desenvolvimento: { tarefas: [] }, mensagensInternas: [], auditoria: [] }; function contarRegistrosReaisDB(banco) { if (!banco || typeof banco !== 'object') return 0; return ( (Array.isArray(banco.clientes) ? banco.clientes.length : 0) + (Array.isArray(banco.propostas) ? banco.propostas.length : 0) + (Array.isArray(banco.vendas) ? banco.vendas.length : 0) + (Array.isArray(banco.pos) ? banco.pos.length : 0) + (Array.isArray(banco.estoque) ? banco.estoque.length : 0) + (Array.isArray(banco.sns) ? banco.sns.length : 0) + (Array.isArray(banco.manuais) ? banco.manuais.length : 0) + (Array.isArray(banco.ordensServicoMRO) ? banco.ordensServicoMRO.length : 0) + (Array.isArray(banco.demandasPosVenda) ? banco.demandasPosVenda.length : 0) + (Array.isArray(banco.defense?.clientes) ? banco.defense.clientes.length : 0) + (Array.isArray(banco.defense?.consultorias) ? banco.defense.consultorias.length : 0) + (Array.isArray(banco.defense?.contratos) ? banco.defense.contratos.length : 0) + (Array.isArray(banco.defense?.estoque) ? banco.defense.estoque.length : 0) ); } function mesclarListasPorChave(listaPrincipal, listaSecundaria, campoChave, excluidosSet) { const mapa = new Map(); const resultado = []; const adicionar = (item, sobrescrever) => { if (!item || typeof item !== 'object') return; const valChave = String(item[campoChave] ?? ''); if (!valChave) return; if (excluidosSet && excluidosSet.has(`${campoChave}:${valChave}`)) return; if (!mapa.has(valChave)) { mapa.set(valChave, item); resultado.push(item); } else if (sobrescrever) { const idx = resultado.findIndex(x => String(x[campoChave] ?? '') === valChave); if (idx >= 0) resultado[idx] = item; mapa.set(valChave, item); } }; (listaPrincipal || []).forEach(it => adicionar(it, false)); (listaSecundaria || []).forEach(it => adicionar(it, false)); return resultado; } function mesclarBancosSemPerda(bancoMaisRecente, bancoMaisAntigo) { if (!bancoMaisAntigo || contarRegistrosReaisDB(bancoMaisAntigo) === 0) return bancoMaisRecente; if (!bancoMaisRecente || contarRegistrosReaisDB(bancoMaisRecente) === 0) return bancoMaisAntigo; const clonado = JSON.parse(JSON.stringify(bancoMaisRecente)); const excluidosArr = Array.from(new Set([...(bancoMaisRecente.registrosExcluidos || []), ...(bancoMaisAntigo.registrosExcluidos || [])])); clonado.registrosExcluidos = excluidosArr; const excluidosSet = new Set(excluidosArr); clonado.clientes = mesclarListasPorChave(bancoMaisRecente.clientes, bancoMaisAntigo.clientes, 'id', excluidosSet); clonado.propostas = mesclarListasPorChave(bancoMaisRecente.propostas, bancoMaisAntigo.propostas, 'numero', excluidosSet); clonado.vendas = mesclarListasPorChave(bancoMaisRecente.vendas, bancoMaisAntigo.vendas, 'id', excluidosSet); clonado.pos = mesclarListasPorChave(bancoMaisRecente.pos, bancoMaisAntigo.pos, 'numero', excluidosSet); clonado.oportunidades = mesclarListasPorChave(bancoMaisRecente.oportunidades, bancoMaisAntigo.oportunidades, 'id', excluidosSet); clonado.anamneses = mesclarListasPorChave(bancoMaisRecente.anamneses, bancoMaisAntigo.anamneses, 'id', excluidosSet); clonado.fornecedores = mesclarListasPorChave(bancoMaisRecente.fornecedores, bancoMaisAntigo.fornecedores, 'id', excluidosSet); clonado.pns = mesclarListasPorChave(bancoMaisRecente.pns, bancoMaisAntigo.pns, 'pn', excluidosSet); clonado.estoque = mesclarListasPorChave(bancoMaisRecente.estoque, bancoMaisAntigo.estoque, 'id', excluidosSet); clonado.movimentacoesEstoque = mesclarListasPorChave(bancoMaisRecente.movimentacoesEstoque, bancoMaisAntigo.movimentacoesEstoque, 'id', excluidosSet); clonado.sns = mesclarListasPorChave(bancoMaisRecente.sns, bancoMaisAntigo.sns, 'sn', excluidosSet); clonado.integracoes = mesclarListasPorChave(bancoMaisRecente.integracoes, bancoMaisAntigo.integracoes, 'id', excluidosSet); clonado.logbook = mesclarListasPorChave(bancoMaisRecente.logbook, bancoMaisAntigo.logbook, 'id', excluidosSet); clonado.demandasPosVenda = mesclarListasPorChave(bancoMaisRecente.demandasPosVenda, bancoMaisAntigo.demandasPosVenda, 'protocolo', excluidosSet); clonado.manuais = mesclarListasPorChave(bancoMaisRecente.manuais, bancoMaisAntigo.manuais, 'id', excluidosSet); clonado.ordensServicoMRO = mesclarListasPorChave(bancoMaisRecente.ordensServicoMRO, bancoMaisAntigo.ordensServicoMRO, 'id', excluidosSet); if (clonado.financeiro) { clonado.financeiro.lancamentos = mesclarListasPorChave( bancoMaisRecente.financeiro?.lancamentos, bancoMaisAntigo.financeiro?.lancamentos, 'id', excluidosSet ); } if (clonado.defense) { clonado.defense.clientes = mesclarListasPorChave(bancoMaisRecente.defense?.clientes, bancoMaisAntigo.defense?.clientes, 'id', excluidosSet); clonado.defense.consultorias = mesclarListasPorChave(bancoMaisRecente.defense?.consultorias, bancoMaisAntigo.defense?.consultorias, 'id', excluidosSet); clonado.defense.contratos = mesclarListasPorChave(bancoMaisRecente.defense?.contratos, bancoMaisAntigo.defense?.contratos, 'id', excluidosSet); clonado.defense.estoque = mesclarListasPorChave(bancoMaisRecente.defense?.estoque, bancoMaisAntigo.defense?.estoque, 'id', excluidosSet); if (clonado.defense.financeiro) { clonado.defense.financeiro.lancamentos = mesclarListasPorChave( bancoMaisRecente.defense?.financeiro?.lancamentos, bancoMaisAntigo.defense?.financeiro?.lancamentos, 'id', excluidosSet ); } } if (clonado.desenvolvimento) { clonado.desenvolvimento.tarefas = mesclarListasPorChave( bancoMaisRecente.desenvolvimento?.tarefas, bancoMaisAntigo.desenvolvimento?.tarefas, 'id', excluidosSet ); } return clonado; } function carregarBancoInicial() { let candidatoPrincipal = null; let candidatoSnapshot = null; try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) { const parsed = JSON.parse(raw); if (parsed && typeof parsed === 'object') { candidatoPrincipal = parsed; } } } catch (e) {} try { const rawSnap = localStorage.getItem(LAST_NON_EMPTY_BACKUP_KEY); if (rawSnap) { const parsedSnap = JSON.parse(rawSnap); if (parsedSnap && typeof parsedSnap === 'object') { candidatoSnapshot = parsedSnap; } } } catch (e) {} // Se a chave principal estava vazia mas temos um snapshot seguro com pedidos/clientes, restaura do snapshot! if (contarRegistrosReaisDB(candidatoPrincipal) === 0 && contarRegistrosReaisDB(candidatoSnapshot) > 0) { return candidatoSnapshot; } if (candidatoPrincipal && candidatoSnapshot) { return mesclarBancosSemPerda(candidatoPrincipal, candidatoSnapshot); } if (candidatoPrincipal) return candidatoPrincipal; if (candidatoSnapshot) return candidatoSnapshot; return JSON.parse(JSON.stringify(initialDB)); } let db = carregarBancoInicial(); function garantirEstruturaDB() { if (!db.productionEpoch || Number(db.productionEpoch) < PRODUCTION_RESET_EPOCH) { db.productionEpoch = PRODUCTION_RESET_EPOCH; } if (!Array.isArray(db.registrosExcluidos)) db.registrosExcluidos = []; if (!Array.isArray(db.clientes)) db.clientes = []; if (!Array.isArray(db.oportunidades)) db.oportunidades = []; if (!Array.isArray(db.anamneses)) db.anamneses = []; if (!Array.isArray(db.propostas)) db.propostas = []; if (!Array.isArray(db.vendas)) db.vendas = []; if (!Array.isArray(db.pos)) db.pos = []; if (!Array.isArray(db.fornecedores)) db.fornecedores = []; if (!Array.isArray(db.pns) || db.pns.length === 0) db.pns = JSON.parse(JSON.stringify(initialDB.pns)); if (!Array.isArray(db.estoque)) db.estoque = []; if (!Array.isArray(db.movimentacoesEstoque)) db.movimentacoesEstoque = []; if (!Array.isArray(db.sns)) db.sns = []; if (!Array.isArray(db.integracoes)) db.integracoes = []; if (!Array.isArray(db.logbook)) db.logbook = []; if (!Array.isArray(db.demandasPosVenda)) db.demandasPosVenda = []; if (!Array.isArray(db.manuais)) db.manuais = []; if (!db.financeiro) db.financeiro = JSON.parse(JSON.stringify(initialDB.financeiro)); if (!Array.isArray(db.financeiro.lancamentos)) db.financeiro.lancamentos = []; if (!Array.isArray(db.financeiro.planoContas) || db.financeiro.planoContas.length === 0) { db.financeiro.planoContas = JSON.parse(JSON.stringify(initialDB.financeiro.planoContas)); } if (!Array.isArray(db.ordensServicoMRO)) db.ordensServicoMRO = []; if (!db.senhasUsuarios || typeof db.senhasUsuarios !== 'object') db.senhasUsuarios = {}; if (!Array.isArray(db.comunicadosMatheus)) db.comunicadosMatheus = []; if (!Array.isArray(db.mensagensInternas)) db.mensagensInternas = []; if (!Array.isArray(db.auditoria)) db.auditoria = []; if (!db.defense || typeof db.defense !== 'object') { db.defense = JSON.parse(JSON.stringify(initialDB.defense)); } if (!Array.isArray(db.defense.clientes)) db.defense.clientes = []; if (!Array.isArray(db.defense.consultorias)) db.defense.consultorias = []; if (!Array.isArray(db.defense.contratos)) db.defense.contratos = []; if (!Array.isArray(db.defense.estoque)) db.defense.estoque = []; if (!Array.isArray(db.defense.movimentacoesEstoque)) db.defense.movimentacoesEstoque = []; if (!db.defense.financeiro || typeof db.defense.financeiro !== 'object') { db.defense.financeiro = { lancamentos: [] }; } if (!Array.isArray(db.defense.financeiro.lancamentos)) { db.defense.financeiro.lancamentos = []; } if (!db.desenvolvimento || typeof db.desenvolvimento !== 'object') { db.desenvolvimento = { tarefas: [] }; } if (!Array.isArray(db.desenvolvimento.tarefas)) { db.desenvolvimento.tarefas = []; } // Consolidar eventuais linhas duplicadas de mesmo P/N + Lote no Estoque em uma única linha com a quantidade somada if (Array.isArray(db.estoque) && db.estoque.length > 1) { const mapaEst = new Map(); const consolidadoEst = []; db.estoque.forEach(item => { const chave = `${normalizarTexto(item.pn)}||${normalizarTexto(item.lote || '')}`; if (mapaEst.has(chave)) { const ex = mapaEst.get(chave); ex.qtd = Number(ex.qtd || 0) + Number(item.qtd || 0); if (item.sn && !String(ex.sn || '').includes(item.sn)) { ex.sn = ex.sn ? `${ex.sn} / ${item.sn}` : item.sn; } } else { mapaEst.set(chave, item); consolidadoEst.push(item); } }); db.estoque = consolidadoEst; } if (Array.isArray(db.defense.estoque) && db.defense.estoque.length > 1) { const mapaDefEst = new Map(); const consolidadoDefEst = []; db.defense.estoque.forEach(item => { const chave = normalizarTexto(item.pn); if (mapaDefEst.has(chave)) { const ex = mapaDefEst.get(chave); ex.qtd = Number(ex.qtd || 0) + Number(item.qtd || 0); if (item.sn && !String(ex.sn || '').includes(item.sn)) { ex.sn = ex.sn ? `${ex.sn} / ${item.sn}` : item.sn; } } else { mapaDefEst.set(chave, item); consolidadoDefEst.push(item); } }); db.defense.estoque = consolidadoDefEst; } db.desenvolvimento.tarefas.forEach(t => { if (!t.escopo) { const pNorm = normalizarTexto(t.projeto || ''); t.escopo = (pNorm.includes('defense') || pNorm.includes('anti-drone')) ? 'DEFENSE' : 'DRONES'; } }); db.pos.forEach(p => { if (!p.posVendaPrep) { p.posVendaPrep = { statusPrep: 'Aguardando chegada p/ integração', responsavelPrep: 'Gabriel Bezerra / Ramon Dias', observacaoPrep: 'Monitorar chegada do pedido para agendar instalação, cadastro de S.N. e treinamento do cliente.' }; } if (!p.tracking) { p.tracking = { etapaCodigo: '2_PO_EMITIDO', localAtual: 'Graz, Áustria (Fabricante)', processoAtual: 'Aguardando despacho do fabricante', awb: '', diDuimp: '', eta: p.previsao || '', obs: 'P.O. registrado no sistema', atualizadoPor: p.criadoPor || 'Sistema', atualizadoEm: p.criadoEm || carimboHoraAtual(), historico: [] }; } }); // Garante cálculo retroativo de 4% de comissão e sincronização contábil em todas as propostas e contratos (db.propostas || []).forEach(prop => { if (!prop) return; if (!prop.vendedor) prop.vendedor = prop.criadoPor || 'Lucas Azevedo'; prop.comissaoPerc = 4; prop.comissaoValor = Number((Number(prop.total || 0) * TAXA_COMISSAO_VENDEDOR).toFixed(2)); if (typeof sincronizarPropostaComVendaPOEContabil === 'function' && prop.status !== 'RECUSADA' && prop.status !== 'CANCELADA') { sincronizarPropostaComVendaPOEContabil(prop, prop.numero); } }); (db.defense?.contratos || []).forEach(ct => { if (!ct) return; if (!ct.vendedor) ct.vendedor = ct.criadoPor || 'Jean Natividade'; ct.comissaoPerc = 4; ct.comissaoValor = Number((Number(ct.valor || 0) * TAXA_COMISSAO_VENDEDOR).toFixed(2)); if (typeof sincronizarContratoDefenseComContabil === 'function' && ct.status !== 'Cancelado / Recusado') { sincronizarContratoDefenseComContabil(ct, ct.id); } }); } garantirEstruturaDB(); restaurarArquivosDoIndexedDB(); function gerarCopiaLeveSemBinariosPesados(origemDB) { const dbLeve = JSON.parse(JSON.stringify(origemDB)); (dbLeve.pos || []).forEach(p => { if (p.anexosPO) { ['proposta', 'cotacaoDRS', 'comprovantePagto'].forEach(k => { if (p.anexosPO[k] && p.anexosPO[k].url && p.anexosPO[k].url.length > 80000) { p.anexosPO[k].url = 'ARQUIVO_VALIDADO_EM_MEMORIA'; } }); } }); (dbLeve.propostas || []).forEach(pr => { if (pr.dossieVendas) { ['proposta', 'cotacaoDRS', 'comprovantePagto'].forEach(k => { if (pr.dossieVendas[k] && pr.dossieVendas[k].url && pr.dossieVendas[k].url.length > 80000) { pr.dossieVendas[k].url = 'ARQUIVO_VALIDADO_EM_MEMORIA'; } }); } }); (dbLeve.manuais || []).forEach(m => { if (m.pdf && m.pdf.startsWith('data:') && m.pdf.length > 80000) { salvarArquivoNoIndexedDB(`MAN_PDF_${m.id}`, m.pdf); m.pdf = 'IDB_STORED'; } }); (dbLeve.desenvolvimento?.tarefas || []).forEach(t => { (t.subtarefas || []).forEach(s => { (s.anexos || []).forEach(anx => { if (anx.url && anx.url.startsWith('data:') && anx.url.length > 80000) { salvarArquivoNoIndexedDB(`SUBDEV_DOC_${s.id}_${anx.id}`, anx.url); anx.url = 'IDB_STORED'; } }); }); }); (dbLeve.sns || []).forEach(s => { (s.anexos || []).forEach(a => { if (a.url && a.url.length > 80000) a.url = 'ARQUIVO_VALIDADO_EM_MEMORIA'; }); }); return dbLeve; } function safeSaveLocalStorage() { // 1. Tenta salvar completo no localStorage; se exceder a cota de 5MB por causa de um PDF de manual, // move os binários pesados para o IndexedDB e salva a versão leve 100% íntegra no localStorage! try { localStorage.setItem(STORAGE_KEY, JSON.stringify(db)); } catch (quotaErr) { try { const dbLeve = gerarCopiaLeveSemBinariosPesados(db); localStorage.setItem(STORAGE_KEY, JSON.stringify(dbLeve)); } catch (e2) {} } // 2. Se o banco tem registros reais (pedidos, clientes, propostas, etc.), salva também no Snapshot Seguro Anti-Perda! if (contarRegistrosReaisDB(db) > 0) { try { const snapLeve = gerarCopiaLeveSemBinariosPesados(db); localStorage.setItem(LAST_NON_EMPTY_BACKUP_KEY, JSON.stringify(snapLeve)); } catch (eSnap) {} } } function estaEmModoEdicaoSquarespace() { try { if (window.parent && window.parent !== window && window.parent.document && window.parent.document.body) { const cls = window.parent.document.body.className || ''; if (cls.includes('sqs-edit-mode')) return true; } } catch (e) {} return false; } function saveDB() { const agora = Date.now(); db.updatedAt = agora; ultimoSaveLocal = agora; safeSaveLocalStorage(); renderAll(); enviarDadosParaNuvemSilencioso(); } async function enviarDadosParaNuvemSilencioso(forcarEnvio = false) { if (!URL_GOOGLE_APPS_SCRIPT) return; // TRAVA ANTI-PERDA DE PEDIDOS: // Nunca permite que uma instância recém-aberta e vazia (ou o preview do editor do Squarespace) sobrescreva a nuvem com dados vazios! if (!forcarEnvio && contarRegistrosReaisDB(db) === 0) { return; } if (!forcarEnvio && estaEmModoEdicaoSquarespace() && !sessaoAtual.perfil) { return; } try { atualizarIndicadorNuvem('sync', 'Salvando na nuvem...'); // Envia cópia otimizada se o payload total ultrapassar 3.5MB (evita rejeição de limite no Apps Script por PDFs gigantes) let payloadStr = JSON.stringify(db); if (payloadStr.length > 3500000) { payloadStr = JSON.stringify(gerarCopiaLeveSemBinariosPesados(db)); } await fetch(URL_GOOGLE_APPS_SCRIPT, { method: "POST", mode: "no-cors", headers: { "Content-Type": "text/plain;charset=utf-8" }, body: payloadStr }); atualizarIndicadorNuvem('ok', 'Nuvem Sincronizada (' + new Date().toLocaleTimeString('pt-BR').slice(0,5) + ')'); } catch (e) { atualizarIndicadorNuvem('erro', 'Salvo Localmente'); } } async function sincronizarComNuvemAgora(exibirAlerta) { if (sincronizandoAgora || !URL_GOOGLE_APPS_SCRIPT) return; if (!exibirAlerta && (Date.now() - ultimoSaveLocal < 12000)) return; sincronizandoAgora = true; try { atualizarIndicadorNuvem('sync', 'Verificando nuvem...'); const resp = await fetch(URL_GOOGLE_APPS_SCRIPT + '?t=' + Date.now(), { method: 'GET', redirect: 'follow' }); if (resp.ok) { const nuvemDB = await resp.json(); if (nuvemDB && Array.isArray(nuvemDB.clientes)) { const qtdNuvem = contarRegistrosReaisDB(nuvemDB); const qtdLocal = contarRegistrosReaisDB(db); const tsNuvem = Number(nuvemDB.updatedAt || 0); const tsLocal = Number(db.updatedAt || 0); if (qtdNuvem === 0 && qtdLocal > 0) { // Nuvem está vazia, mas o navegador local tem pedidos/dados cadastrados -> Sobe os dados locais para a nuvem! db.updatedAt = Date.now(); safeSaveLocalStorage(); await enviarDadosParaNuvemSilencioso(true); } else if (qtdLocal === 0 && qtdNuvem > 0) { // Navegador local está vazio, mas a nuvem tem dados -> Puxa os dados da nuvem! db = nuvemDB; garantirEstruturaDB(); restaurarArquivosDoIndexedDB(); safeSaveLocalStorage(); renderAll(); } else if (qtdNuvem > 0 && qtdLocal > 0) { // Ambos têm dados -> Faz o Merge Aditivo Sem Perda para garantir que nenhum pedido/cliente/manual suma! if (tsNuvem >= tsLocal) { db = mesclarBancosSemPerda(nuvemDB, db); garantirEstruturaDB(); restaurarArquivosDoIndexedDB(); safeSaveLocalStorage(); renderAll(); } else { db = mesclarBancosSemPerda(db, nuvemDB); garantirEstruturaDB(); restaurarArquivosDoIndexedDB(); safeSaveLocalStorage(); renderAll(); await enviarDadosParaNuvemSilencioso(true); } } atualizarIndicadorNuvem('ok', 'Nuvem Sincronizada (' + new Date().toLocaleTimeString('pt-BR').slice(0,5) + ')'); if (exibirAlerta) alert('Base sincronizada e protegida com sucesso com o Google Drive da LES Aviation!'); } else if (nuvemDB && nuvemDB.status === 'empty' && contarRegistrosReaisDB(db) > 0) { await enviarDadosParaNuvemSilencioso(true); } } } catch (err) { atualizarIndicadorNuvem('ok', 'Base Local Ativa'); } finally { sincronizandoAgora = false; } } function registrarAuditoria(modulo, acao, refId) { const nomeAreas = { diretoria: 'Diretoria Holding', diretor_drones: 'Diretoria Drones (LES Aviation)', diretor_defense: 'Diretoria LES Defense', defense_tecnico: 'Engenharia/Técnico LES Defense', defense: 'Subsidiária LES Defense', vendedor: 'Comercial LES Aviation', logistica: 'Logística LES Aviation', pos_venda: 'MRO LES Aviation' }; db.auditoria.unshift({ dataHora: carimboHoraAtual(), usuario: `${sessaoAtual.nome || 'Sistema'} (${nomeAreas[sessaoAtual.perfil] || 'N/A'})`, modulo, acao, refId: refId || '-' }); } function obterSenhaAtualConta(conta) { if (!conta) return ''; const chave = normalizarTexto(conta.nome); if (db.senhasUsuarios && db.senhasUsuarios[chave]) { return db.senhasUsuarios[chave]; } return conta.senhaPadrao; } async function fazerLogin(e) { if (e) e.preventDefault(); garantirEstruturaDB(); const userDigitado = normalizarTexto(document.getElementById('login-user').value); const passDigitada = document.getElementById('login-pass').value.trim(); const conta = USUARIOS_AUTORIZADOS.find(u => normalizarTexto(u.nome) === userDigitado && obterSenhaAtualConta(u) === passDigitada); if (!conta) { alert('Nome de usuário ou senha inválidos.'); return; } sessaoAtual = { perfil: conta.perfilPrincipal, areasPermitidas: conta.areasPermitidas, nome: conta.nome, cargo: conta.cargo, escopoSubsidiaria: conta.escopoSubsidiaria || 'DRONES', acessoTotal: conta.acessoTotal, ehMaster: !!conta.ehMaster }; document.getElementById('login-screen').classList.add('hidden'); document.getElementById('app-container').classList.remove('hidden'); document.getElementById('user-badge').textContent = conta.nome; document.getElementById('user-sub-badge').textContent = conta.cargo; document.getElementById('header-perfil-nome').textContent = `${conta.nome} • ${conta.cargo}`; document.getElementById('header-avatar').textContent = conta.nome.split(' ').map(p => p[0]).join('').slice(0, 2).toUpperCase(); // Mostrar botões de Sincronizar, Exportar, Importar e Enviar Aviso Geral APENAS no perfil Master do Matheus const masterSyncBox = document.getElementById('sidebar-master-sync-controls'); const btnAvisoMatheus = document.getElementById('btn-header-aviso-matheus'); if (usuarioEhMasterMatheus()) { if (masterSyncBox) masterSyncBox.classList.remove('hidden'); if (btnAvisoMatheus) { btnAvisoMatheus.classList.remove('hidden'); btnAvisoMatheus.classList.add('flex'); } } else { if (masterSyncBox) masterSyncBox.classList.add('hidden'); if (btnAvisoMatheus) { btnAvisoMatheus.classList.add('hidden'); btnAvisoMatheus.classList.remove('flex'); } } // Preencher responsável/vendedor padrão nos formulários com o usuário logado ['dev-tar-resp', 'def-cons-resp', 'mro-tecnico', 'prop-vendedor', 'def-cont-vendedor'].forEach(idCampo => { const elCampo = document.getElementById(idCampo); if (elCampo) { if (elCampo.tagName === 'SELECT') { if (!Array.from(elCampo.options).some(opt => opt.value === conta.nome)) { const opt = document.createElement('option'); opt.value = conta.nome; opt.textContent = conta.nome; elCampo.appendChild(opt); } } elCampo.value = conta.nome; } }); restaurarEstadoSidebar(); montarMenuLateralRBAC(); registrarAuditoria('AUTENTICACAO', `Login efetuado (${conta.cargo})`, conta.nome); renderAll(); sincronizarComNuvemAgora(false); switchTab(conta.abaInicial || 'vendas'); verificarExibicaoUnicaAvisoGeral(); } function fazerLogout() { registrarAuditoria('AUTENTICACAO', 'Logout efetuado', sessaoAtual.nome); saveDB(); sessaoAtual = { perfil: null, areasPermitidas: [], nome: null, cargo: null, acessoTotal: false, ehMaster: false }; document.getElementById('app-container').classList.add('hidden'); document.getElementById('login-screen').classList.remove('hidden'); document.getElementById('login-pass').value = ''; } // ========================================================= // SISTEMA DE TROCA DE SENHAS (IDS UNIFICADOS) // ========================================================= function abrirModalTrocarSenha() { if (!sessaoAtual.nome) return; document.getElementById('troca-senha-usuario').value = `${sessaoAtual.nome} (${sessaoAtual.cargo || ''})`; document.getElementById('troca-senha-atual').value = ''; document.getElementById('troca-senha-nova').value = ''; document.getElementById('troca-senha-confirma').value = ''; document.getElementById('modal-trocar-senha').classList.remove('hidden'); } function fecharModalTrocarSenha() { document.getElementById('modal-trocar-senha').classList.add('hidden'); } function salvarNovaSenhaUsuario(e) { e.preventDefault(); if (!sessaoAtual.nome) return; const conta = USUARIOS_AUTORIZADOS.find(u => normalizarTexto(u.nome) === normalizarTexto(sessaoAtual.nome)); if (!conta) return; const senhaAtualDigitada = document.getElementById('troca-senha-atual').value.trim(); const senhaNova = document.getElementById('troca-senha-nova').value.trim(); const senhaConfirma = document.getElementById('troca-senha-confirma').value.trim(); if (senhaAtualDigitada !== obterSenhaAtualConta(conta)) { alert('A senha atual informada está incorreta.'); return; } if (senhaNova.length < 4) { alert('A nova senha deve ter pelo menos 4 caracteres.'); return; } if (senhaNova !== senhaConfirma) { alert('A confirmação da nova senha não confere.'); return; } db.senhasUsuarios = db.senhasUsuarios || {}; db.senhasUsuarios[normalizarTexto(conta.nome)] = senhaNova; registrarAuditoria('SEGURANCA', `Alterou a própria senha de acesso no sistema`, conta.nome); saveDB(); fecharModalTrocarSenha(); alert('Senha alterada com sucesso!'); } // ========================================================= // AVISO GERAL DO PERFIL DO MATHEUS (APARECE 1 VEZ E NÃO APARECE MAIS APÓS DAR CIÊNCIA) // ========================================================= function obterAvisoGeralAtivo() { if (!Array.isArray(db.comunicadosMatheus) || db.comunicadosMatheus.length === 0) return null; return db.comunicadosMatheus[0]; } function verificarExibicaoUnicaAvisoGeral() { if (!sessaoAtual.nome) return; const aviso = obterAvisoGeralAtivo(); if (!aviso) return; aviso.lidoPor = Array.isArray(aviso.lidoPor) ? aviso.lidoPor : []; const meuNomeNorm = normalizarTexto(sessaoAtual.nome); if (aviso.lidoPor.includes(meuNomeNorm)) { document.getElementById('modal-aviso-ciencia')?.classList.add('hidden'); return; } const elPrio = document.getElementById('aviso-ciencia-prioridade'); const elTit = document.getElementById('aviso-ciencia-titulo'); const elMeta = document.getElementById('aviso-ciencia-meta'); const elTxt = document.getElementById('aviso-ciencia-texto'); if (elPrio) { elPrio.textContent = aviso.prioridade || 'Comunicado Geral'; elPrio.className = 'px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase inline-block mb-1 ' + (aviso.prioridade === 'Urgente' ? 'bg-rose-600 text-white' : aviso.prioridade === 'Importante' ? 'bg-amber-500 text-zinc-950' : 'bg-emerald-600 text-white'); } if (elTit) elTit.textContent = aviso.titulo || 'Aviso Geral'; if (elMeta) elMeta.textContent = `Enviado por ${aviso.autor || 'Matheus Azevedo'} em ${aviso.dataHora || ''}`; if (elTxt) elTxt.textContent = aviso.mensagem || ''; document.getElementById('modal-aviso-ciencia')?.classList.remove('hidden'); } function confirmarCienciaAvisoGeral() { if (!sessaoAtual.nome) return; const aviso = obterAvisoGeralAtivo(); if (!aviso) { document.getElementById('modal-aviso-ciencia')?.classList.add('hidden'); return; } const meuNomeNorm = normalizarTexto(sessaoAtual.nome); aviso.lidoPor = Array.isArray(aviso.lidoPor) ? aviso.lidoPor : []; if (!aviso.lidoPor.includes(meuNomeNorm)) { aviso.lidoPor.push(meuNomeNorm); } document.getElementById('modal-aviso-ciencia')?.classList.add('hidden'); registrarAuditoria('COMUNICACAO', `Confirmou ciência do aviso geral: "${aviso.titulo}"`, aviso.id); saveDB(); } function abrirModalGerenciarAvisoMatheus() { if (!usuarioEhMasterMatheus()) return; renderCorreioEAlertas(); document.getElementById('modal-aviso-matheus-admin')?.classList.remove('hidden'); } function fecharModalGerenciarAvisoMatheus() { document.getElementById('modal-aviso-matheus-admin')?.classList.add('hidden'); } function enviarComunicadoMatheus(e) { e.preventDefault(); if (!usuarioEhMasterMatheus()) { alert('Apenas o perfil Master do Matheus pode publicar avisos gerais.'); return; } const titulo = document.getElementById('mural-titulo').value.trim(); const prioridade = document.getElementById('mural-prioridade').value; const mensagem = document.getElementById('mural-texto').value.trim(); if (!titulo || !mensagem) return; const novoCom = { id: 'AVISO-' + Date.now().toString().slice(-5), titulo, prioridade, mensagem, autor: sessaoAtual.nome, dataHora: carimboHoraAtual(), lidoPor: [normalizarTexto(sessaoAtual.nome)] }; db.comunicadosMatheus = [novoCom, ...(db.comunicadosMatheus || []).slice(0, 9)]; registrarAuditoria('COMUNICACAO', `Matheus publicou novo Aviso Geral para todos: "${titulo}"`, novoCom.id); e.target.reset(); saveDB(); renderCorreioEAlertas(); alert('Aviso Geral publicado! Todos os usuários receberão o aviso automaticamente na tela até darem ciência.'); } function renderCorreioEAlertas() { if (!sessaoAtual.nome) return; verificarExibicaoUnicaAvisoGeral(); const painelMatheus = document.getElementById('painel-status-ciencia-matheus'); if (!painelMatheus) return; const aviso = obterAvisoGeralAtivo(); if (!aviso) { painelMatheus.innerHTML = `<div class="text-zinc-400 italic">Nenhum aviso geral ativo no momento.</div>`; return; } const lidos = aviso.lidoPor || []; const badgesEquipe = USUARIOS_AUTORIZADOS.map(u => { const ok = lidos.includes(normalizarTexto(u.nome)); return `<span class="px-2.5 py-1 rounded-lg text-[10px] font-bold border ${ok ? 'bg-emerald-50 border-emerald-300 text-emerald-900' : 'bg-amber-50 border-amber-200 text-amber-800'}">${ok ? '✓ Ciente:' : '⏳ Pendente:'} ${u.nome}</span>`; }).join(' '); painelMatheus.innerHTML = ` <div class="flex justify-between items-center"> <b class="text-zinc-950">${aviso.titulo} (${aviso.prioridade})</b> <span class="text-[10px] font-mono text-zinc-500">${aviso.dataHora}</span> </div> <p class="text-zinc-700 bg-zinc-50 p-2.5 rounded-lg border">${aviso.mensagem}</p> <div class="pt-1"> <span class="text-[10px] font-bold uppercase text-zinc-500 block mb-1.5">Confirmação de Ciência por Usuário:</span> <div class="flex flex-wrap gap-1.5">${badgesEquipe}</div> </div> `; } function usuarioUsaAbasCompletasDefense() { if (!sessaoAtual.perfil) return false; if (usuarioEhDiretoriaHolding()) return false; return usuarioTemArea('diretor_defense') || usuarioTemArea('defense_tecnico') || usuarioTemArea('defense'); } function obterLogoHTMLDocumento(subtituloExtra = '') { let customLogo = null; try { customLogo = localStorage.getItem(LOGO_STORAGE_KEY); } catch (e) {} const logoVisual = customLogo ? `<img src="${customLogo}" alt="LES Aviation" style="max-height:54px; width:auto; display:block; object-fit:contain;">` : `<svg viewBox="0 0 360 155" style="width:142px; height:auto; display:block;" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M22 18 V102 H98" stroke="#0A0A0A" stroke-width="15" stroke-linecap="square" stroke-linejoin="miter"/> <line x1="128" y1="25.5" x2="218" y2="25.5" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="60" x2="218" y2="60" stroke="#0A0A0A" stroke-width="15"/> <line x1="128" y1="94.5" x2="218" y2="94.5" stroke="#0A0A0A" stroke-width="15"/> <path d="M332 25.5 H275 C255 25.5 246 35 246 43.5 C246 54 256 60 274 60 H308 C328 60 338 68 338 78.5 C338 89 327 94.5 306 94.5 H244" stroke="#0A0A0A" stroke-width="15"/> <text x="180" y="142" text-anchor="middle" fill="#0A0A0A" font-family="Inter, sans-serif" font-size="23" font-weight="500" letter-spacing="14">AVIATION</text> </svg>`; return ` <div> ${logoVisual} ${subtituloExtra ? `<div style="font-size:8.5px; color:#52525b; text-transform:uppercase; letter-spacing:0.12em; font-weight:700; margin-top:5px;">${subtituloExtra}</div>` : ''} </div> `; } let abaAtivaMenuId = 'diretoria'; function montarMenuLateralRBAC() { const nav = document.getElementById('sidebar-nav'); if (!nav) return; const permitidos = obterModulosLiberadosSessao(); const qtdFinAv = db.financeiro.lancamentos.filter(l => !l.estornado).length; const qtdFinDef = (db.defense?.financeiro?.lancamentos || []).filter(l => !l.estornado).length; const contadores = { diretoria: db.propostas.length + (db.defense?.contratos || []).length, financeiro: usuarioEhDiretoriaHolding() ? (qtdFinAv + qtdFinDef) : usuarioEhDiretorDefense() ? qtdFinDef : qtdFinAv, vendas: db.propostas.length, defense: ((db.defense?.consultorias || []).length + (db.defense?.contratos || []).length + (db.defense?.clientes || []).length + (db.defense?.estoque || []).length), def_consultoria: (db.defense?.consultorias || []).length, def_contratos: (db.defense?.contratos || []).length, def_clientes: (db.defense?.clientes || []).length, def_estoque: (db.defense?.estoque || []).length, desenvolvimento: obterTarefasDesenvolvimentoVisiveis().length, logistica: db.pos.filter(p => p.status !== 'Cancelado').length, estoque: db.estoque.length, posvenda: (db.ordensServicoMRO || []).length + db.sns.length }; let itensMenu = []; if (usuarioUsaAbasCompletasDefense()) { itensMenu = [ { id: 'def_consultoria', icon: 'fa-satellite-dish', label: 'Consultoria & Site Survey' }, { id: 'def_contratos', icon: 'fa-file-shield', label: 'Projetos & Contratos' }, { id: 'def_clientes', icon: 'fa-building-shield', label: 'Clientes Defense' }, { id: 'def_estoque', icon: 'fa-boxes-stacked', label: 'Estoque Defense' } ]; if (permitidos.includes('financeiro')) { itensMenu.push({ id: 'financeiro', icon: 'fa-sack-dollar', label: 'Financeiro Defense' }); } if (permitidos.includes('desenvolvimento')) { itensMenu.push({ id: 'desenvolvimento', icon: 'fa-list-check', label: 'Desenvolvimento' }); } } else { itensMenu = MENU_DEFINICAO.filter(item => permitidos.includes(item.id)); } nav.innerHTML = itensMenu.map(item => ` <button onclick="switchTab('${item.id}')" id="nav-${item.id}" title="${item.label}" class="nav-btn ${abaAtivaMenuId === item.id ? 'active-nav' : ''} w-full flex items-center justify-between gap-2.5 px-3.5 py-3 text-zinc-400 hover:bg-zinc-900 hover:text-white transition"> <div class="flex items-center gap-3 min-w-0"> <i class="fa-solid ${item.icon} w-4 text-center text-sm shrink-0"></i> <span class="tracking-wide uppercase text-[11px] truncate sidebar-hide-collapsed">${item.label}</span> </div> <span class="nav-badge-count px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-zinc-800 text-zinc-300 shrink-0">${contadores[item.id] || 0}</span> </button> `).join(''); } function abrirAbaDefenseEspecifica(sub) { if (usuarioUsaAbasCompletasDefense()) { switchTab('def_' + sub); } else { switchTab('defense'); switchSubTab('def', sub); } } function switchTab(tabId) { const permitidos = obterModulosLiberadosSessao(); const mapaSubDefense = { def_consultoria: 'consultoria', def_contratos: 'contratos', def_clientes: 'clientes', def_estoque: 'estoque' }; if (tabId === 'defense' && usuarioUsaAbasCompletasDefense()) { tabId = 'def_consultoria'; } const moduloReal = mapaSubDefense[tabId] ? 'defense' : tabId; if (!permitidos.includes(moduloReal)) { exigirPermissao([], `Acesso à aba ${tabId}`); return; } abaAtivaMenuId = tabId; document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); const target = document.getElementById('tab-' + moduloReal); if (target) target.classList.add('active'); const defSubtabsBar = document.getElementById('def-subtabs-bar'); if (defSubtabsBar) { if (usuarioUsaAbasCompletasDefense()) defSubtabsBar.classList.add('hidden'); else defSubtabsBar.classList.remove('hidden'); } if (mapaSubDefense[tabId]) { switchSubTab('def', mapaSubDefense[tabId]); const titulosBannerDef = { consultoria: 'Módulo de Consultoria Técnica & Diagnóstico de Espaço Aéreo (Site Survey)', contratos: 'Módulo Comercial B2B — Projetos, Propostas & Contratos LES Defense', clientes: 'Base PrópriA de Clientes Corporativos & Institucionais — LES Defense', estoque: 'Almoxarifado Exclusivo de Sensores Passivos & Hardware Tático — LES Defense' }; const subBanner = document.getElementById('def-header-subtitle'); if (subBanner && titulosBannerDef[mapaSubDefense[tabId]]) { subBanner.textContent = titulosBannerDef[mapaSubDefense[tabId]]; } } else if (moduloReal === 'defense') { const subBanner = document.getElementById('def-header-subtitle'); if (subBanner) { subBanner.textContent = 'Consultoria (Site Survey) • Projetos & Contratos B2B • Base Própria de Clientes Defense • Estoque Exclusivo Defense'; } } document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active-nav')); const activeBtn = document.getElementById('nav-' + tabId); if (activeBtn) activeBtn.classList.add('active-nav'); const titulos = { diretoria: 'Dashboard — Gestão Executiva, Indicadores & Governança', financeiro: 'Financeiro — DRE, Fluxo de Caixa & Balanço Patrimonial', vendas: 'Vendas — Propostas Executivas, Clientes & Status do Pedido', defense: 'LES Defense — Consultoria de Espaço Aéreo (Site Survey) & Serviços de Detecção Anti-Drone', def_consultoria: 'LES Defense — Consultoria & Diagnóstico de Espaço Aéreo (Site Survey)', def_contratos: 'LES Defense — Projetos, Propostas & Contratos B2B', def_clientes: 'LES Defense — Cadastro & Carteira Exclusiva de Clientes Anti-Drone', def_estoque: 'LES Defense — Estoque Exclusivo de Sensores & Equipamentos Táticos', desenvolvimento: 'Desenvolvimento — Etapas, Tarefas, Subtarefas, Barra de Progresso & Prazos', logistica: 'Logística — Purchase Orders (P.O.) & Edição de Rastreamento', estoque: 'Estoque — Almoxarifado Aeronáutico, Lotes & Auditoria Contábil', posvenda: 'Pós-venda e MRO — Acompanhamento de P.O., Demandas, O.S. & Manuais Técnicos' }; const pt = document.getElementById('page-title'); if (pt && titulos[tabId]) pt.textContent = titulos[tabId]; if (moduloReal === 'diretoria' && usuarioEhDiretoria()) { setTimeout(renderGraficosDiretoria, 80); } if (moduloReal === 'financeiro' && usuarioTemVisaoFinanceira()) { aplicarEscopoVisaoFinanceiraPorPerfil(); renderFinanceiro(); } } function switchSubTab(prefix, sub) { document.querySelectorAll(`.subtab-${prefix}`).forEach(el => el.classList.add('hidden')); const target = document.getElementById(`subtab-${prefix}-${sub}`); if (target) target.classList.remove('hidden'); document.querySelectorAll(`[id^="subbtn-${prefix}-"]`).forEach(b => b.classList.remove('active-sub')); const btn = document.getElementById(`subbtn-${prefix}-${sub}`); if (btn) btn.classList.add('active-sub'); } function adicionarMesesData(dataISO, meses) { if (!dataISO) return ''; const [y, m, d] = dataISO.split('-').map(Number); const dt = new Date(y, m - 1 + Number(meses), d); return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`; } function diasRestantes(dataStr) { if (!dataStr) return 9999; const hoje = new Date(); hoje.setHours(0,0,0,0); const [y, m, d] = dataStr.split('-').map(Number); return Math.ceil((new Date(y, m - 1, d) - hoje) / (1000 * 60 * 60 * 24)); } function fmtBRL(v) { if (v === null || v === undefined || isNaN(v)) return 'R$ 0,00'; return Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }); } function fmtDataBR(iso) { if (!iso) return '—'; const [y, m, d] = iso.slice(0, 10).split('-'); return d && m && y ? `${d}/${m}/${y}` : iso; } function obterOpcoesSelectPN(pnSelecionado = '') { const opcoesBase = db.pns.map(p => `<option value="${p.pn}" ${p.pn === pnSelecionado ? 'selected' : ''}>${p.pn} — ${p.desc}</option>` ).join(''); const extra = pnSelecionado && !db.pns.some(p => p.pn === pnSelecionado) ? `<option value="${pnSelecionado}" selected>${pnSelecionado}</option>` : ''; return `<option value="">-- Selecionar P/N da Base --</option>${opcoesBase}${extra}`; } function aoSelecionarPNLinhaCotacao(selectEl) { const pnEscolhido = selectEl.value; const row = selectEl.closest('.prop-item-row'); if (!row) return; const inputProd = row.querySelector('.item-prod'); const pnObj = db.pns.find(p => p.pn === pnEscolhido); if (pnObj && inputProd) { inputProd.value = pnObj.desc; } recalcularPreviewProposta(); } function adicionarLinhaItemProposta(prod = '', pn = '', qtd = 1, unit = '') { const container = document.getElementById('prop-itens-container'); if (!container) return; const pnInicial = pn || (db.pns[0] ? db.pns[0].pn : ''); const prodInicial = prod || (db.pns.find(x => x.pn === pnInicial)?.desc || ''); const row = document.createElement('div'); row.className = 'grid grid-cols-1 md:grid-cols-12 gap-2 items-center prop-item-row'; row.innerHTML = ` <select onchange="aoSelecionarPNLinhaCotacao(this)" required class="md:col-span-3 border rounded-lg px-2 py-1.5 bg-white font-mono text-xs item-pn"> ${obterOpcoesSelectPN(pnInicial)} </select> <input type="text" required placeholder="Nome / Descrição do Produto" value="${prodInicial}" class="md:col-span-5 border rounded-lg px-2.5 py-1.5 bg-white item-prod"> <input type="number" min="1" required placeholder="Qtd" value="${qtd}" oninput="recalcularPreviewProposta()" class="md:col-span-1 border rounded-lg px-2 py-1.5 bg-white item-qtd"> <input type="number" step="0.01" required placeholder="Valor Unit. (R$)" value="${unit}" oninput="recalcularPreviewProposta()" class="md:col-span-2 border rounded-lg px-2.5 py-1.5 bg-white item-unit"> <button type="button" onclick="this.parentElement.remove(); recalcularPreviewProposta();" class="md:col-span-1 bg-rose-100 text-rose-700 py-1.5 rounded-lg font-bold hover:bg-rose-200">×</button> `; container.appendChild(row); recalcularPreviewProposta(); } function coletarItensFormularioProposta() { const rows = Array.from(document.querySelectorAll('.prop-item-row')); return rows.map(r => { const prod = r.querySelector('.item-prod').value.trim(); const pn = r.querySelector('.item-pn').value.trim(); const qtd = parseFloat(r.querySelector('.item-qtd').value) || 1; const unit = parseFloat(r.querySelector('.item-unit').value) || 0; return { produto: prod, pn, qtd, unit, total: qtd * unit }; }); } function usuarioEhResponsavelPelaVenda(nomeVendedor) { if (!sessaoAtual.nome || !nomeVendedor) return false; return normalizarTexto(sessaoAtual.nome) === normalizarTexto(nomeVendedor); } function calcularValoresPagamentoProposta(valorCheioBruto, formaPagamento, parcelasCartao) { const valorCheio = Number(Number(valorCheioBruto || 0).toFixed(2)); const forma = formaPagamento || 'PIX'; const parcelas = Math.max(1, parseInt(parcelasCartao) || 4); const descontoPix5 = Number((valorCheio * 0.05).toFixed(2)); const valorPixComDesconto = Number((valorCheio - descontoPix5).toFixed(2)); const valorParcelaCartao = Number((valorCheio / parcelas).toFixed(2)); const descontoAplicado = forma === 'PIX' ? descontoPix5 : 0; const totalFinal = forma === 'PIX' ? valorPixComDesconto : valorCheio; const comissaoVendedor4 = Number((totalFinal * TAXA_COMISSAO_VENDEDOR).toFixed(2)); let resumoPagamento = ''; let regraSinalSugerida = ''; if (forma === 'PIX') { resumoPagamento = `PIX c/ 5% de Desconto (-${fmtBRL(descontoPix5)}) • Total: ${fmtBRL(valorPixComDesconto)}`; regraSinalSugerida = `PIX c/ 5% Desc. (30% no pedido: ${fmtBRL(valorPixComDesconto * 0.30)} | 70% na entrega: ${fmtBRL(valorPixComDesconto * 0.70)})`; } else if (forma === 'CARTAO') { resumoPagamento = `Cartão de Crédito em ${parcelas}x de ${fmtBRL(valorParcelaCartao)}${parcelas <= 4 ? ' sem juros' : ''} • Total: ${fmtBRL(valorCheio)}`; regraSinalSugerida = `Cartão de Crédito: ${parcelas}x de ${fmtBRL(valorParcelaCartao)}${parcelas <= 4 ? ' sem juros' : ''}`; } else { resumoPagamento = `Dinheiro (À Vista) • Total: ${fmtBRL(valorCheio)}`; regraSinalSugerida = `Dinheiro (30% no pedido: ${fmtBRL(valorCheio * 0.30)} | 70% na entrega: ${fmtBRL(valorCheio * 0.70)})`; } return { valorCheio, formaPagamento: forma, parcelasCartao: parcelas, descontoPix: descontoAplicado, descontoPix5, valorPixComDesconto, valorParcelaCartao, totalFinal, comissaoPerc: 4, comissaoVendedor4, resumoPagamento, regraSinalSugerida }; } function aoMudarFormaPagamentoProposta() { recalcularPreviewProposta(true); } function recalcularPreviewProposta(atualizarRegraSinal = false) { const itens = coletarItensFormularioProposta(); const subtotal = itens.reduce((a, b) => a + b.total, 0); const frete = parseFloat(document.getElementById('prop-frete')?.value) || 0; const taxaImpPerc = Math.max(0, parseFloat(document.getElementById('prop-taxa-imp-perc')?.value) || 0); const taxas = Number((subtotal * (taxaImpPerc / 100)).toFixed(2)); const elTaxasHidden = document.getElementById('prop-taxas-imp'); if (elTaxasHidden) elTaxasHidden.value = String(taxas); const elTaxaHint = document.getElementById('prop-taxa-imp-reais-preview'); if (elTaxaHint) { elTaxaHint.textContent = `Taxa (${taxaImpPerc}% s/ produtos): ${fmtBRL(taxas)}`; } const valorCheio = subtotal + frete + taxas; const forma = document.getElementById('prop-forma-pagamento')?.value || 'PIX'; const parcelas = parseInt(document.getElementById('prop-parcelas-cartao')?.value) || 4; const calc = calcularValoresPagamentoProposta(valorCheio, forma, parcelas); // Mostrar/ocultar seletor de parcelas do cartão e alternar painéis para exibir APENAS o modo selecionado const boxParcelas = document.getElementById('box-prop-parcelas-cartao'); const boxModoPix = document.getElementById('box-modo-pagto-pix'); const boxModoCartao = document.getElementById('box-modo-pagto-cartao'); const boxModoDinheiro = document.getElementById('box-modo-pagto-dinheiro'); if (boxParcelas) { if (forma === 'CARTAO') boxParcelas.classList.remove('hidden'); else boxParcelas.classList.add('hidden'); } if (boxModoPix) boxModoPix.classList.toggle('hidden', forma !== 'PIX'); if (boxModoCartao) boxModoCartao.classList.toggle('hidden', forma !== 'CARTAO'); if (boxModoDinheiro) boxModoDinheiro.classList.toggle('hidden', forma !== 'DINHEIRO'); const elCheio = document.getElementById('prop-valor-cheio-preview'); if (elCheio) elCheio.value = fmtBRL(calc.valorCheio); const elPix = document.getElementById('prop-valor-pix-preview'); if (elPix) elPix.value = fmtBRL(calc.valorPixComDesconto); const elEcon = document.getElementById('prop-economia-pix-preview'); if (elEcon) elEcon.textContent = `Desconto de 5% aplicado (-${fmtBRL(calc.descontoPix5)})`; const elCartao = document.getElementById('prop-valor-cartao-preview'); if (elCartao) elCartao.value = `${calc.parcelasCartao}x de ${fmtBRL(calc.valorParcelaCartao)}${calc.parcelasCartao <= 4 ? ' sem juros' : ''}`; const elSubCartao = document.getElementById('prop-sub-cartao-preview'); if (elSubCartao) elSubCartao.textContent = `Opção selecionada: ${calc.parcelasCartao}x ${calc.parcelasCartao <= 4 ? 'sem juros' : 'no cartão'}`; const elTotCartaoBadge = document.getElementById('prop-total-cartao-badge'); if (elTotCartaoBadge) elTotCartaoBadge.textContent = fmtBRL(calc.valorCheio); const gridOpcoesCartao = document.getElementById('grid-opcoes-parcelas-cartao-preview'); if (gridOpcoesCartao && forma === 'CARTAO') { gridOpcoesCartao.innerHTML = [1, 2, 3, 4].map(n => { const vParc = Number((calc.valorCheio / n).toFixed(2)); const sel = n === calc.parcelasCartao; return `<div class="p-2 rounded-lg border text-center ${sel ? 'bg-sky-900 text-white border-sky-950 font-extrabold shadow-sm' : 'bg-white text-sky-950 border-sky-200 font-semibold'}">${n}x de ${fmtBRL(vParc)} s/ juros</div>`; }).join(''); } const elTotal = document.getElementById('prop-total-preview'); if (elTotal) elTotal.value = fmtBRL(calc.totalFinal); const elResumoAtivo = document.getElementById('prop-resumo-pagto-ativo'); if (elResumoAtivo) elResumoAtivo.textContent = calc.resumoPagamento; // Continua calculando os 4% e mostra apenas se o usuário logado for o responsável pela venda (sem label escrita de comissão) const vendedorSel = document.getElementById('prop-vendedor')?.value || sessaoAtual.nome || 'Lucas Azevedo'; const boxRetornoResp = document.getElementById('box-retorno-responsavel-preview'); const elComissao = document.getElementById('prop-comissao-vendedor-preview'); const elSubCom = document.getElementById('prop-sub-comissao-preview'); if (elComissao) elComissao.value = fmtBRL(calc.comissaoVendedor4); if (elSubCom) elSubCom.textContent = `Responsável pela Venda: ${vendedorSel}`; if (boxRetornoResp) { if (usuarioEhResponsavelPelaVenda(vendedorSel)) { boxRetornoResp.classList.remove('hidden'); } else { boxRetornoResp.classList.add('hidden'); } } const elSinal = document.getElementById('prop-sinal-regra'); if (elSinal && (atualizarRegraSinal || !elSinal.value || elSinal.value === '30% no ato do pedido' || elSinal.value.startsWith('PIX c/ 5%') || elSinal.value.startsWith('Cartão de Crédito:') || elSinal.value.startsWith('Dinheiro ('))) { elSinal.value = calc.regraSinalSugerida; } } function sincronizarPropostaComVendaPOEContabil(prop, numAntigo = null) { if (!prop) return false; let alterouContabil = false; const numAtual = String(prop.numero); const numRefAntigo = numAntigo ? String(numAntigo) : numAtual; const cli = db.clientes.find(c => Number(c.id) === Number(prop.clienteId)); const nomeCli = cli ? cli.nome : ('Cliente #' + prop.clienteId); const enderecoEntregaCli = cli ? `${cli.endereco || ''}${cli.cidade && !(cli.endereco || '').includes(cli.cidade) ? ' — ' + cli.cidade : ''}` : 'Endereço conforme cadastro do cliente'; const vendedorNome = prop.vendedor || prop.criadoPor || 'Vendedor LES'; prop.vendedor = vendedorNome; prop.comissaoPerc = 4; prop.comissaoValor = Number((Number(prop.total || 0) * TAXA_COMISSAO_VENDEDOR).toFixed(2)); const numPO = prop.poVinculado || ('PO-' + numAtual); const docRefNovo = `PROP-${numAtual} / ${numPO}`; const agora = carimboHoraAtual(); // 1. Atualiza registro de Venda vinculado (se existir) const vendaVinc = (db.vendas || []).find(v => String(v.propostaNumero) === numRefAntigo || String(v.propostaNumero) === numAtual || v.id === ('VEN-' + numRefAntigo) || v.id === ('VEN-' + numAtual) ); if (vendaVinc) { if (Number(vendaVinc.valorAutorizado) !== Number(prop.total) || Number(vendaVinc.comissaoValor || 0) !== Number(prop.comissaoValor) || vendaVinc.vendedor !== vendedorNome || String(vendaVinc.propostaNumero) !== numAtual || vendaVinc.produto !== prop.produto || Number(vendaVinc.clienteId) !== Number(prop.clienteId) || vendaVinc.formaPagamento !== prop.formaPagamento || Number(vendaVinc.parcelasCartao || 4) !== Number(prop.parcelasCartao || 4)) { vendaVinc.id = 'VEN-' + numAtual; vendaVinc.propostaNumero = prop.numero; vendaVinc.clienteId = prop.clienteId; vendaVinc.vendedor = vendedorNome; vendaVinc.comissaoPerc = 4; vendaVinc.comissaoValor = prop.comissaoValor; vendaVinc.produto = prop.produto; vendaVinc.fabricante = prop.fabricante; vendaVinc.qtd = prop.qtd; vendaVinc.valorAutorizado = prop.total; vendaVinc.valorCheio = prop.valorCheio || prop.total; vendaVinc.formaPagamento = prop.formaPagamento || 'PIX'; vendaVinc.parcelasCartao = prop.parcelasCartao || 4; vendaVinc.resumoPagamento = prop.resumoPagamento || ''; alterouContabil = true; } } // 2. Atualiza Purchase Order (P.O.) vinculado (se existir) const poVinc = (db.pos || []).find(po => String(po.refVenda) === numRefAntigo || String(po.refVenda) === numAtual || po.numero === prop.poVinculado || po.numero === ('PO-' + numRefAntigo) || po.numero === ('PO-' + numAtual) ); if (poVinc) { if (Number(poVinc.valorNum) !== Number(prop.total) || Number(poVinc.subtotal) !== Number(prop.subtotal) || Number(poVinc.frete || 0) !== Number(prop.frete || 0) || Number(poVinc.taxasImp || 0) !== Number(prop.taxasImp || 0) || poVinc.vendedor !== vendedorNome || Number(poVinc.comissaoValor || 0) !== Number(prop.comissaoValor) || poVinc.produto !== prop.produto || Number(poVinc.clienteId) !== Number(prop.clienteId)) { poVinc.refVenda = prop.numero; poVinc.clienteId = prop.clienteId; poVinc.vendedor = vendedorNome; poVinc.comissaoPerc = 4; poVinc.comissaoValor = prop.comissaoValor; poVinc.enderecoEntrega = enderecoEntregaCli || poVinc.enderecoEntrega; poVinc.fornecedor = prop.fabricante || poVinc.fornecedor; poVinc.produto = prop.produto; poVinc.itens = prop.itens || []; poVinc.qtd = prop.qtd; poVinc.subtotal = prop.subtotal !== undefined ? prop.subtotal : prop.total; poVinc.frete = prop.frete || 0; poVinc.taxasImp = prop.taxasImp || 0; poVinc.valorCheio = prop.valorCheio || prop.total; poVinc.formaPagamento = prop.formaPagamento || 'PIX'; poVinc.parcelasCartao = prop.parcelasCartao || 4; poVinc.resumoPagamento = prop.resumoPagamento || ''; poVinc.valorNum = prop.total; poVinc.valor = fmtBRL(prop.total); alterouContabil = true; } if (prop.dossieVendas) { poVinc.anexosPO = poVinc.anexosPO || {}; if (prop.dossieVendas.cotacaoDRS) poVinc.anexosPO.cotacaoDRS = prop.dossieVendas.cotacaoDRS; if (prop.dossieVendas.comprovantePagto) poVinc.anexosPO.comprovantePagto = prop.dossieVendas.comprovantePagto; } } // 3. Atualiza Lançamentos Contábeis / Financeiros na aba Financeiro (db.financeiro.lancamentos) const valorTotal = Number(prop.total || 0); const valorSinal30 = Number((valorTotal * 0.30).toFixed(2)); const valorSaldo70 = Number((valorTotal - valorSinal30).toFixed(2)); const custoFreteImp = Number((prop.frete || 0) + (prop.taxasImp || 0)); const valorComissao4 = Number((valorTotal * TAXA_COMISSAO_VENDEDOR).toFixed(2)); const sufixoPagto = prop.resumoPagamento ? ` • [${prop.resumoPagamento}]` : ''; const lancSin = (db.financeiro?.lancamentos || []).find(l => l.id === `FIN-SIN-${numRefAntigo}` || l.id === `FIN-SIN-${numAtual}` ); const lancSal = (db.financeiro?.lancamentos || []).find(l => l.id === `FIN-SAL-${numRefAntigo}` || l.id === `FIN-SAL-${numAtual}` ); const lancImp = (db.financeiro?.lancamentos || []).find(l => l.id === `FIN-IMP-${numRefAntigo}` || l.id === `FIN-IMP-${numAtual}` ); const lancCom = (db.financeiro?.lancamentos || []).find(l => l.id === `FIN-COM-${numRefAntigo}` || l.id === `FIN-COM-${numAtual}` ); if (lancSin) { const novoLiquidoSin = Number((valorSinal30 - Number(lancSin.impostos || 0) + Number(lancSin.ajuste || 0)).toFixed(2)); const novaDescSin = `Sinal 30% Cotação Nº ${prop.numero} (${prop.produto})${sufixoPagto}`; if (Number(lancSin.bruto) !== valorSinal30 || Number(lancSin.liquido) !== novoLiquidoSin || lancSin.entidade !== nomeCli || lancSin.doc !== docRefNovo || lancSin.desc !== novaDescSin) { lancSin.id = `FIN-SIN-${numAtual}`; lancSin.doc = docRefNovo; lancSin.entidade = nomeCli; lancSin.desc = novaDescSin; lancSin.bruto = valorSinal30; lancSin.liquido = novoLiquidoSin; lancSin.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Edição Proposta)`; lancSin.modificadoEm = agora; alterouContabil = true; } } if (lancSal) { const novoLiquidoSal = Number((valorSaldo70 - Number(lancSal.impostos || 0) + Number(lancSal.ajuste || 0)).toFixed(2)); const novaDescSal = `Saldo 70% na Entrega — Cotação Nº ${prop.numero}${sufixoPagto}`; if (Number(lancSal.bruto) !== valorSaldo70 || Number(lancSal.liquido) !== novoLiquidoSal || lancSal.entidade !== nomeCli || lancSal.doc !== docRefNovo || lancSal.desc !== novaDescSal) { lancSal.id = `FIN-SAL-${numAtual}`; lancSal.doc = docRefNovo; lancSal.entidade = nomeCli; lancSal.desc = novaDescSal; lancSal.bruto = valorSaldo70; lancSal.liquido = novoLiquidoSal; lancSal.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Edição Proposta)`; lancSal.modificadoEm = agora; alterouContabil = true; } } // Sincroniza o lançamento contábil da Comissão de 4% do Vendedor if (lancCom) { const novaEntidadeCom = `Vendedor: ${vendedorNome} (Comissão 4%)`; const novaDescCom = `Comissão Comercial 4% (${vendedorNome}) — Cotação Nº ${prop.numero} (4% s/ Conta ${fmtBRL(valorTotal)})`; if (Number(lancCom.bruto) !== valorComissao4 || Number(lancCom.liquido) !== valorComissao4 || lancCom.entidade !== novaEntidadeCom || lancCom.doc !== docRefNovo || lancCom.desc !== novaDescCom) { lancCom.id = `FIN-COM-${numAtual}`; lancCom.doc = docRefNovo; lancCom.entidade = novaEntidadeCom; lancCom.desc = novaDescCom; lancCom.bruto = valorComissao4; lancCom.liquido = valorComissao4; if (lancCom.estornado && prop.status !== 'RECUSADA' && prop.status !== 'CANCELADA') { lancCom.estornado = false; lancCom.status = prop.comissaoStatus === 'PAGA' ? 'REALIZADO' : 'PREVISTO'; } lancCom.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Comissão 4%)`; lancCom.modificadoEm = agora; alterouContabil = true; } } else if (valorComissao4 > 0 && (lancSin || lancSal) && prop.status !== 'RECUSADA' && prop.status !== 'CANCELADA') { const hojeISO = prop.emissao || new Date().toISOString().slice(0, 10); db.financeiro.lancamentos.unshift({ id: `FIN-COM-${numAtual}`, natureza: 'DESPESA_OP', categoria: 'Comissões Comerciais (4%)', desc: `Comissão Comercial 4% (${vendedorNome}) — Cotação Nº ${prop.numero} (4% s/ Conta ${fmtBRL(valorTotal)})`, cc: 'Comercial / Vendas', entidade: `Vendedor: ${vendedorNome} (Comissão 4%)`, doc: docRefNovo, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: valorComissao4, impostos: 0, ajuste: 0, liquido: valorComissao4, status: prop.comissaoStatus === 'PAGA' ? 'REALIZADO' : 'PREVISTO', conta: 'Banco Principal LES', conciliado: prop.comissaoStatus === 'PAGA', estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Comissão 4%)`, criadoEm: agora }); alterouContabil = true; } if (lancImp) { if (custoFreteImp > 0) { const novoLiqImp = Number((custoFreteImp - Number(lancImp.impostos || 0) + Number(lancImp.ajuste || 0)).toFixed(2)); if (Number(lancImp.bruto) !== custoFreteImp || Number(lancImp.liquido) !== novoLiqImp || lancImp.estornado) { lancImp.id = `FIN-IMP-${numAtual}`; lancImp.doc = docRefNovo; lancImp.desc = `Provisão Frete + Taxas de Importação (${numPO})`; lancImp.bruto = custoFreteImp; lancImp.liquido = novoLiqImp; if (lancImp.estornado && prop.status !== 'RECUSADA' && prop.status !== 'CANCELADA') { lancImp.estornado = false; lancImp.status = 'PREVISTO'; } lancImp.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Edição Proposta)`; lancImp.modificadoEm = agora; alterouContabil = true; } } else if (!lancImp.estornado) { lancImp.estornado = true; lancImp.status = 'ESTORNADO'; lancImp.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Frete/Imp Zerado na Proposta)`; lancImp.modificadoEm = agora; alterouContabil = true; } } else if (custoFreteImp > 0 && (lancSin || lancSal || prop.status === 'APROVADA' || prop.status === 'ACEITA')) { const hojeISO = prop.emissao || new Date().toISOString().slice(0, 10); db.financeiro.lancamentos.unshift({ id: `FIN-IMP-${numAtual}`, natureza: 'CPV_CMV', categoria: 'Importação / Compras Internacionais', desc: `Provisão Frete + Taxas de Importação (${numPO})`, cc: 'Logística & Importação', entidade: prop.fabricante || 'Fornecedor Internacional', doc: docRefNovo, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: custoFreteImp, impostos: 0, ajuste: 0, liquido: custoFreteImp, status: 'PREVISTO', conta: 'Banco Principal LES', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Proposta->Fin)`, criadoEm: agora }); alterouContabil = true; } // 4. Caso exista algum lançamento avulso no Financeiro vinculado diretamente ao doc da Proposta/PO (sem ser SIN/SAL/IMP/COM) (db.financeiro?.lancamentos || []).forEach(l => { if (l.estornado) return; if (l.id === `FIN-SIN-${numAtual}` || l.id === `FIN-SAL-${numAtual}` || l.id === `FIN-IMP-${numAtual}` || l.id === `FIN-COM-${numAtual}`) return; const docStr = String(l.doc || '').trim(); const bateDoc = docStr === numRefAntigo || docStr === numAtual || docStr === `PROP-${numRefAntigo}` || docStr === `PROP-${numAtual}` || docStr === `PO-${numRefAntigo}` || docStr === `PO-${numAtual}` || l.id === `FIN-PO-${numRefAntigo}` || l.id === `FIN-PO-${numAtual}` || l.id === `FIN-PROP-${numRefAntigo}` || l.id === `FIN-PROP-${numAtual}`; if (bateDoc && !lancSin && !lancSal) { const novoLiq = Number((valorTotal - Number(l.impostos || 0) + Number(l.ajuste || 0)).toFixed(2)); if (Number(l.bruto) !== valorTotal || Number(l.liquido) !== novoLiq) { l.bruto = valorTotal; l.liquido = novoLiq; l.entidade = nomeCli || l.entidade; l.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Edição Proposta)`; l.modificadoEm = agora; alterouContabil = true; } } }); // 5. Se a proposta ainda não tinha lançamento contábil criado, cria/integra automaticamente para refletir na aba Contábil if (!lancSin && !lancSal && prop.status !== 'RECUSADA' && prop.status !== 'CANCELADA') { integrarPropostaEPOAoFinanceiro(prop, numPO, nomeCli, prop.emissao || new Date().toISOString().slice(0, 10)); alterouContabil = true; } return alterouContabil; } function salvarProposta(e) { e.preventDefault(); garantirEstruturaDB(); const editOriginalNum = document.getElementById('prop-edit-original-num').value.trim(); const numero = document.getElementById('prop-numero').value.trim(); if (!editOriginalNum && db.propostas.some(p => String(p.numero) === String(numero))) { alert('Já existe uma cotação/proposta com este número. Use o botão "Editar" na tabela abaixo se quiser alterá-la.'); return; } const itens = coletarItensFormularioProposta(); if (itens.length === 0) { alert('Adicione ao menos 1 produto na proposta.'); return; } const subtotal = itens.reduce((a, b) => a + b.total, 0); const frete = parseFloat(document.getElementById('prop-frete').value) || 0; const taxaImpPerc = Math.max(0, parseFloat(document.getElementById('prop-taxa-imp-perc')?.value) || 0); const taxasImp = Number((subtotal * (taxaImpPerc / 100)).toFixed(2)); const valorCheio = subtotal + frete + taxasImp; const formaPagamento = document.getElementById('prop-forma-pagamento')?.value || 'PIX'; const parcelasCartao = parseInt(document.getElementById('prop-parcelas-cartao')?.value) || 4; const calcPagto = calcularValoresPagamentoProposta(valorCheio, formaPagamento, parcelasCartao); const total = calcPagto.totalFinal; const vendedor = document.getElementById('prop-vendedor')?.value || sessaoAtual.nome || 'Lucas Azevedo'; const cotacaoFabRef = document.getElementById('prop-cotacao-fab-ref').value.trim(); const comprovanteRef = document.getElementById('prop-comprovante-ref').value.trim(); const agora = carimboHoraAtual(); if (editOriginalNum) { const idx = db.propostas.findIndex(p => String(p.numero) === String(editOriginalNum)); if (idx >= 0) { const antiga = db.propostas[idx]; const dossieVendas = antiga.dossieVendas || { cotacaoDRS: null, comprovantePagto: null }; if (cotacaoFabRef) { dossieVendas.cotacaoDRS = { ...(dossieVendas.cotacaoDRS || {}), checked: true, info: cotacaoFabRef, nome: dossieVendas.cotacaoDRS?.nome || `Cotacao_Fab_${cotacaoFabRef}.pdf`, url: dossieVendas.cotacaoDRS?.url || 'VALIDADO', enviadoPor: sessaoAtual.nome, data: agora }; } if (comprovanteRef) { dossieVendas.comprovantePagto = { ...(dossieVendas.comprovantePagto || {}), checked: true, info: comprovanteRef, nome: dossieVendas.comprovantePagto?.nome || `Comprovante_${comprovanteRef}.pdf`, url: dossieVendas.comprovantePagto?.url || 'VALIDADO', enviadoPor: sessaoAtual.nome, data: agora }; } db.propostas[idx] = { ...antiga, numero, emissao: document.getElementById('prop-emissao').value || antiga.emissao, validade: document.getElementById('prop-validade').value.trim() || '15 DIAS', fabricante: document.getElementById('prop-fabricante').value.trim(), clienteId: Number(document.getElementById('prop-cliente').value), vendedor, comissaoPerc: 4, comissaoValor: calcPagto.comissaoVendedor4, signatarioCliente: document.getElementById('prop-signatario-cli').value.trim(), consultorContato: document.getElementById('prop-consultor-contato').value.trim(), cotacaoFabRef, comprovanteRef, dossieVendas, itens, produto: itens.map(i => `${i.qtd}x ${i.produto} (${i.pn})`).join(' + '), qtd: itens.reduce((a, b) => a + b.qtd, 0), subtotal, frete, taxaImpPerc, taxasImp, valorCheio: calcPagto.valorCheio, formaPagamento: calcPagto.formaPagamento, parcelasCartao: calcPagto.parcelasCartao, descontoPix: calcPagto.descontoPix, valorPixComDesconto: calcPagto.valorPixComDesconto, valorParcelaCartao: calcPagto.valorParcelaCartao, resumoPagamento: calcPagto.resumoPagamento, total, sinalRegra: document.getElementById('prop-sinal-regra').value.trim() || calcPagto.regraSinalSugerida, exclusoes: document.getElementById('prop-exclusoes').value.trim(), modificadoPor: sessaoAtual.nome, modificadoEm: agora }; sincronizarPropostaComVendaPOEContabil(db.propostas[idx], editOriginalNum); registrarAuditoria('VENDAS', `Editou Proposta Nº ${numero} (Responsável: ${vendedor} • Total: ${fmtBRL(total)})`, numero); cancelarModoEdicaoProposta(); saveDB(); return; } } const dossieInicial = { proposta: { checked: true, nome: `Proposta_LES_${numero}.pdf`, info: `Cotação Nº ${numero}`, url: 'SISTEMA', enviadoPor: sessaoAtual.nome, data: agora }, cotacaoDRS: cotacaoFabRef ? { checked: true, nome: `Cotacao_Fabricante_${numero}.pdf`, info: cotacaoFabRef, url: 'VALIDADO', enviadoPor: sessaoAtual.nome, data: agora } : null, comprovantePagto: comprovanteRef ? { checked: true, nome: `Comprovante_Pagto_${numero}.pdf`, info: comprovanteRef, url: 'VALIDADO', enviadoPor: sessaoAtual.nome, data: agora } : null }; const prop = { numero, emissao: document.getElementById('prop-emissao').value || new Date().toISOString().slice(0, 10), validade: document.getElementById('prop-validade').value.trim() || '15 DIAS', fabricante: document.getElementById('prop-fabricante').value.trim(), clienteId: Number(document.getElementById('prop-cliente').value), vendedor, comissaoPerc: 4, comissaoValor: calcPagto.comissaoVendedor4, comissaoStatus: 'A_RECEBER', signatarioCliente: document.getElementById('prop-signatario-cli').value.trim(), consultorContato: document.getElementById('prop-consultor-contato').value.trim(), cotacaoFabRef, comprovanteRef, dossieVendas: dossieInicial, itens, produto: itens.map(i => `${i.qtd}x ${i.produto} (${i.pn})`).join(' + '), qtd: itens.reduce((a, b) => a + b.qtd, 0), subtotal, frete, taxaImpPerc, taxasImp, valorCheio: calcPagto.valorCheio, formaPagamento: calcPagto.formaPagamento, parcelasCartao: calcPagto.parcelasCartao, descontoPix: calcPagto.descontoPix, valorPixComDesconto: calcPagto.valorPixComDesconto, valorParcelaCartao: calcPagto.valorParcelaCartao, resumoPagamento: calcPagto.resumoPagamento, total, moeda: 'BRL', sinalRegra: document.getElementById('prop-sinal-regra').value.trim() || calcPagto.regraSinalSugerida, exclusoes: document.getElementById('prop-exclusoes').value.trim(), status: 'PENDENTE_APROVACAO', criadoPor: sessaoAtual.nome, criadoEm: agora, aprovadoPor: null, aprovadoEm: null, poVinculado: null }; db.propostas.unshift(prop); const cliNova = db.clientes.find(c => Number(c.id) === Number(prop.clienteId)); integrarPropostaEPOAoFinanceiro(prop, `PO-${prop.numero}`, cliNova ? cliNova.nome : ('Cliente #' + prop.clienteId), prop.emissao); registrarAuditoria('VENDAS', `Gerou Proposta Comercial Nº ${numero} (Responsável: ${vendedor} • Total: ${fmtBRL(total)})`, numero); cancelarModoEdicaoProposta(); saveDB(); } function editarPropostaExistente(numProposta) { const p = db.propostas.find(x => String(x.numero) === String(numProposta)); if (!p) return; document.getElementById('prop-edit-original-num').value = p.numero; document.getElementById('prop-numero').value = p.numero; document.getElementById('prop-emissao').value = p.emissao || ''; document.getElementById('prop-validade').value = p.validade || '15 DIAS'; document.getElementById('prop-fabricante').value = p.fabricante || ''; document.getElementById('prop-cliente').value = p.clienteId; const elVend = document.getElementById('prop-vendedor'); if (elVend) { const vendVal = p.vendedor || p.criadoPor || 'Lucas Azevedo'; if (!Array.from(elVend.options).some(opt => opt.value === vendVal)) { const opt = document.createElement('option'); opt.value = vendVal; opt.textContent = vendVal; elVend.appendChild(opt); } elVend.value = vendVal; } document.getElementById('prop-signatario-cli').value = p.signatarioCliente || ''; document.getElementById('prop-consultor-contato').value = p.consultorContato || ''; document.getElementById('prop-cotacao-fab-ref').value = p.cotacaoFabRef || p.dossieVendas?.cotacaoDRS?.info || ''; document.getElementById('prop-comprovante-ref').value = p.comprovanteRef || p.dossieVendas?.comprovantePagto?.info || ''; document.getElementById('prop-frete').value = p.frete || 0; const subCalc = Number(p.subtotal || 0); const percSalvo = p.taxaImpPerc !== undefined ? Number(p.taxaImpPerc) : (subCalc > 0 && Number(p.taxasImp || 0) > 0 ? Number(((Number(p.taxasImp) / subCalc) * 100).toFixed(2)) : 0); const elTaxaPerc = document.getElementById('prop-taxa-imp-perc'); if (elTaxaPerc) elTaxaPerc.value = percSalvo; const elTaxaHidden = document.getElementById('prop-taxas-imp'); if (elTaxaHidden) elTaxaHidden.value = p.taxasImp || 0; const elForma = document.getElementById('prop-forma-pagamento'); if (elForma) elForma.value = p.formaPagamento || 'PIX'; const elParc = document.getElementById('prop-parcelas-cartao'); if (elParc) elParc.value = String(p.parcelasCartao || 4); document.getElementById('prop-sinal-regra').value = p.sinalRegra || '30% no ato do pedido'; document.getElementById('prop-exclusoes').value = p.exclusoes || ''; const container = document.getElementById('prop-itens-container'); container.innerHTML = ''; const itens = Array.isArray(p.itens) && p.itens.length ? p.itens : [{ produto: p.produto, pn: db.pns[0]?.pn || '', qtd: p.qtd || 1, unit: p.valorCheio || p.total || 0 }]; itens.forEach(it => adicionarLinhaItemProposta(it.produto, it.pn, it.qtd, it.unit)); recalcularPreviewProposta(false); document.getElementById('prop-form-mode-badge').textContent = `MODO DE EDIÇÃO ATIVO • COTAÇÃO Nº ${p.numero}`; document.getElementById('prop-form-titulo').textContent = `Editando Pedido / Cotação Nº ${p.numero}`; document.getElementById('btn-submit-proposta').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5"></i> Salvar Alterações na Cotação Nº ${p.numero} & Atualizar Contábil`; document.getElementById('btn-cancelar-edicao-prop').classList.remove('hidden'); document.getElementById('card-form-proposta').scrollIntoView({ behavior: 'smooth' }); } function cancelarModoEdicaoProposta() { const form = document.getElementById('form-proposta'); if (form) form.reset(); document.getElementById('prop-edit-original-num').value = ''; const elForma = document.getElementById('prop-forma-pagamento'); if (elForma) elForma.value = 'PIX'; const elParc = document.getElementById('prop-parcelas-cartao'); if (elParc) elParc.value = '4'; const elTaxaPerc = document.getElementById('prop-taxa-imp-perc'); if (elTaxaPerc) elTaxaPerc.value = '0'; const elVend = document.getElementById('prop-vendedor'); if (elVend && sessaoAtual.nome) elVend.value = sessaoAtual.nome; document.getElementById('prop-form-mode-badge').textContent = 'Nova Cotação • Catálogo de P/N Integrado'; document.getElementById('prop-form-titulo').textContent = 'Emitir / Editar Cotação Comercial Produto Aeronáutico'; document.getElementById('btn-submit-proposta').innerHTML = `<i class="fa-solid fa-file-signature mr-1.5"></i> Salvar Pedido / Proposta Comercial`; document.getElementById('btn-cancelar-edicao-prop').classList.add('hidden'); document.getElementById('prop-itens-container').innerHTML = ''; adicionarLinhaItemProposta(); } function marcarComissaoVendedorPaga(numProposta) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Liquidar / Pagar Comissão de 4% do Vendedor (Diretoria)')) return; const prop = db.propostas.find(p => String(p.numero) === String(numProposta)); if (!prop) return; const agora = carimboHoraAtual(); prop.comissaoStatus = prop.comissaoStatus === 'PAGA' ? 'A_RECEBER' : 'PAGA'; prop.comissaoPagaEm = prop.comissaoStatus === 'PAGA' ? agora : null; prop.comissaoPagaPor = prop.comissaoStatus === 'PAGA' ? sessaoAtual.nome : null; const lancCom = (db.financeiro?.lancamentos || []).find(l => l.id === `FIN-COM-${prop.numero}`); if (lancCom && !lancCom.estornado) { lancCom.status = prop.comissaoStatus === 'PAGA' ? 'REALIZADO' : 'PREVISTO'; lancCom.conciliado = prop.comissaoStatus === 'PAGA'; lancCom.modificadoPor = sessaoAtual.nome; lancCom.modificadoEm = agora; } registrarAuditoria('FINANCEIRO', `${prop.comissaoStatus === 'PAGA' ? 'Liquidou/Pagou' : 'Reabriu'} comissão de 4% (${fmtBRL(prop.comissaoValor)}) do vendedor ${prop.vendedor || prop.criadoPor} ref. Cotação Nº ${prop.numero}`, prop.numero); saveDB(); } function recusarOuCancelarProposta(numProposta, novoStatus) { acaoCancelamentoPendente = { tipo: 'PROPOSTA', id: String(numProposta), novoStatus }; const labelAcao = novoStatus === 'RECUSADA' ? 'Recusar Cotação/Pedido' : 'Cancelar Pedido Comercial'; document.getElementById('modal-canc-titulo').textContent = `${labelAcao} Nº ${numProposta}`; document.getElementById('modal-canc-motivo').value = novoStatus === 'RECUSADA' ? 'Proposta recusada pelo cliente / revisão de preço' : 'Pedido cancelado'; document.getElementById('modal-cancelamento').classList.remove('hidden'); } function cancelarPedidoPO(numPO) { acaoCancelamentoPendente = { tipo: 'PO', id: String(numPO), novoStatus: 'Cancelado' }; document.getElementById('modal-canc-titulo').textContent = `Cancelar Purchase Order ${numPO}`; document.getElementById('modal-canc-motivo').value = 'Cancelamento logístico / comercial'; document.getElementById('modal-cancelamento').classList.remove('hidden'); } function fecharModalCancelamento() { acaoCancelamentoPendente = null; document.getElementById('modal-cancelamento').classList.add('hidden'); } function confirmarAcaoModalCancelamento() { if (!acaoCancelamentoPendente) return; const motivo = document.getElementById('modal-canc-motivo').value.trim() || 'Cancelado'; const agora = carimboHoraAtual(); if (acaoCancelamentoPendente.tipo === 'PROPOSTA') { const numProposta = acaoCancelamentoPendente.id; const novoStatus = acaoCancelamentoPendente.novoStatus; const prop = db.propostas.find(p => String(p.numero) === String(numProposta)); if (prop) { prop.status = novoStatus; prop.canceladoPor = sessaoAtual.nome; prop.canceladoEm = agora; prop.motivoCancelamento = motivo; if (prop.poVinculado) { const po = db.pos.find(x => x.numero === prop.poVinculado); if (po) { po.status = 'Cancelado'; po.canceladoPor = sessaoAtual.nome; po.canceladoEm = agora; po.motivoCancelamento = motivo; } } const venda = db.vendas.find(v => String(v.propostaNumero) === String(numProposta)); if (venda) { venda.status = `Cancelada (${motivo})`; venda.canceladoPor = sessaoAtual.nome; venda.canceladoEm = agora; } db.financeiro.lancamentos.forEach(l => { if ((l.doc && (l.doc.includes(`PROP-${numProposta}`) || l.doc.includes(`PO-${numProposta}`))) || l.id === `FIN-COM-${numProposta}`) { l.estornado = true; l.status = 'ESTORNADO'; l.modificadoPor = `${sessaoAtual.nome} (Cancelamento Pedido)`; l.modificadoEm = agora; } }); registrarAuditoria('VENDAS', `${novoStatus === 'RECUSADA' ? 'Recusou' : 'Cancelou'} Pedido/Cotação Nº ${numProposta} — Motivo: ${motivo}`, numProposta); } } else if (acaoCancelamentoPendente.tipo === 'PO') { const numPO = acaoCancelamentoPendente.id; const po = db.pos.find(p => String(p.numero) === String(numPO)); if (po) { po.status = 'Cancelado'; po.canceladoPor = sessaoAtual.nome; po.canceladoEm = agora; po.motivoCancelamento = motivo; db.financeiro.lancamentos.forEach(l => { if (l.doc && l.doc.includes(numPO)) { l.estornado = true; l.status = 'ESTORNADO'; l.modificadoPor = `${sessaoAtual.nome} (Cancelamento PO)`; l.modificadoEm = agora; } }); registrarAuditoria('LOGISTICA', `Cancelou P.O. ${numPO} — Motivo: ${motivo}`, numPO); } } fecharModalCancelamento(); saveDB(); } function aprovarPropostaEGerarPO(numProposta) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Aprovar Proposta Comercial e Gerar P.O. (Diretoria Drones / Holding)')) return; const prop = db.propostas.find(p => String(p.numero) === String(numProposta)); if (!prop) return; const agora = carimboHoraAtual(); const hojeISO = new Date().toISOString().slice(0, 10); prop.status = 'APROVADA'; prop.aprovadoPor = sessaoAtual.nome; prop.aprovadoEm = agora; prop.canceladoPor = null; prop.motivoCancelamento = null; const vendedorNome = prop.vendedor || prop.criadoPor || 'Lucas Azevedo'; prop.vendedor = vendedorNome; prop.comissaoPerc = 4; prop.comissaoValor = Number((Number(prop.total || 0) * TAXA_COMISSAO_VENDEDOR).toFixed(2)); const cli = db.clientes.find(c => Number(c.id) === Number(prop.clienteId)); const nomeCli = cli ? cli.nome : 'Cliente #' + prop.clienteId; const enderecoEntregaCli = cli ? `${cli.endereco || ''}${cli.cidade && !(cli.endereco || '').includes(cli.cidade) ? ' — ' + cli.cidade : ''}` : 'Endereço conforme cadastro do cliente'; const idVenda = 'VEN-' + prop.numero; const numPO = 'PO-' + prop.numero; prop.poVinculado = numPO; const dossieProp = prop.dossieVendas || {}; const anexosIniciaisPO = { proposta: { checked: true, nome: `Proposta_LES_${prop.numero}.pdf (Gerada no Sistema)`, info: `Cotação Nº ${prop.numero}`, url: 'SISTEMA', enviadoPor: prop.criadoPor, data: agora }, cotacaoDRS: dossieProp.cotacaoDRS || null, comprovantePagto: dossieProp.comprovantePagto || null }; const todosAnexosOk = !!(anexosIniciaisPO.proposta && anexosIniciaisPO.cotacaoDRS && anexosIniciaisPO.comprovantePagto); const vendaExistente = db.vendas.find(v => String(v.propostaNumero) === String(prop.numero)); if (!vendaExistente) { db.vendas.unshift({ id: idVenda, propostaNumero: prop.numero, clienteId: prop.clienteId, vendedor: vendedorNome, comissaoPerc: 4, comissaoValor: prop.comissaoValor, produto: prop.produto, fabricante: prop.fabricante, qtd: prop.qtd, valorAutorizado: prop.total, moeda: 'BRL', dataAceite: hojeISO, criadoPor: prop.criadoPor, criadoEm: prop.criadoEm, aprovadoPor: sessaoAtual.nome, aprovadoEm: agora, status: `Aprovada — P.O. ${numPO} Gerado`, poGerado: numPO }); } else { vendaExistente.status = `Aprovada — P.O. ${numPO} Gerado`; vendaExistente.valorAutorizado = prop.total; vendaExistente.vendedor = vendedorNome; vendaExistente.comissaoPerc = 4; vendaExistente.comissaoValor = prop.comissaoValor; } const poExistente = db.pos.find(p => p.numero === numPO); if (!poExistente) { db.pos.unshift({ numero: numPO, refVenda: prop.numero, clienteId: prop.clienteId, vendedor: vendedorNome, comissaoPerc: 4, comissaoValor: prop.comissaoValor, enderecoEntrega: enderecoEntregaCli, fornecedor: prop.fabricante || 'Drone Rescue Systems GmbH', produto: prop.produto, itens: prop.itens || [], qtd: prop.qtd, subtotal: prop.subtotal || prop.total, frete: prop.frete || 0, taxasImp: prop.taxasImp || 0, valorNum: prop.total, valor: fmtBRL(prop.total), incoterm: 'EXW Graz / Áustria -> Itajaí/Anápolis - Brasil', emissao: hojeISO, previsao: adicionarMesesData(hojeISO, 1), status: todosAnexosOk ? '2. P.O. Emitido ao Fabricante Internacional' : '1. Pedido Recebido / Em Validação de Dossiê', criadoPor: prop.criadoPor, criadoEm: prop.criadoEm, aprovadoPor: sessaoAtual.nome, aprovadoEm: agora, anexosPO: anexosIniciaisPO, tracking: { etapaCodigo: todosAnexosOk ? '2_PO_EMITIDO' : '1_PEDIDO_RECEBIDO', localAtual: todosAnexosOk ? 'Graz, Áustria (Fabricante DRS)' : 'Comercial LES Aviation (Brasil)', processoAtual: todosAnexosOk ? 'P.O. transmitido ao fabricante para separação/produção' : 'Aguardando Comercial anexar Cotação Fabricante e Comprovante de Pagamento', awb: '', diDuimp: '', eta: adicionarMesesData(hojeISO, 1), obs: 'Pedido aprovado pela Diretoria e integrado à Logística.', atualizadoPor: sessaoAtual.nome, atualizadoEm: agora, historico: [ { dataHora: agora, etapa: todosAnexosOk ? '2. P.O. Emitido ao Fabricante Internacional' : '1. Pedido Recebido / Em Validação de Dossiê', local: todosAnexosOk ? 'Graz, Áustria' : 'Base LES Brasil', processo: 'Aprovação executiva da proposta e abertura do rastreamento logístico', obs: '', usuario: sessaoAtual.nome } ] } }); } integrarPropostaEPOAoFinanceiro(prop, numPO, nomeCli, hojeISO); registrarAuditoria('DIRETORIA', `Aprovou Proposta Nº ${prop.numero} (Vendedor: ${vendedorNome} • Comissão 4%: ${fmtBRL(prop.comissaoValor)}), gerou ${numPO} e iniciou Rastreamento`, numPO); saveDB(); } function integrarPropostaEPOAoFinanceiro(prop, numPO, nomeCli, hojeISO) { const docRef = `PROP-${prop.numero} / ${numPO}`; const jaExiste = db.financeiro.lancamentos.some(l => (l.id === `FIN-SIN-${prop.numero}` || l.id === `FIN-SAL-${prop.numero}`) && !l.estornado); if (jaExiste) { sincronizarPropostaComVendaPOEContabil(prop, prop.numero); return; } const valorTotal = Number(prop.total || 0); const valorSinal30 = Number((valorTotal * 0.30).toFixed(2)); const valorSaldo70 = Number((valorTotal - valorSinal30).toFixed(2)); const custoFreteImp = Number((prop.frete || 0) + (prop.taxasImp || 0)); const vendedorNome = prop.vendedor || prop.criadoPor || 'Vendedor LES'; const valorComissao4 = Number((valorTotal * TAXA_COMISSAO_VENDEDOR).toFixed(2)); const comprovanteJaValidado = !!(prop.dossieVendas && prop.dossieVendas.comprovantePagto); const sufixoPagto = prop.resumoPagamento ? ` • [${prop.resumoPagamento}]` : ''; const agora = carimboHoraAtual(); db.financeiro.lancamentos.unshift({ id: `FIN-SIN-${prop.numero}`, natureza: 'RECEITA', categoria: 'Venda de Equipamentos / Sistemas', desc: `Sinal 30% Cotação Nº ${prop.numero} (${prop.produto})${sufixoPagto}`, cc: 'Comercial / Vendas', entidade: nomeCli, doc: docRef, competencia: hojeISO, vencimento: hojeISO, bruto: valorSinal30, impostos: 0, ajuste: 0, liquido: valorSinal30, status: comprovanteJaValidado ? 'REALIZADO' : 'PREVISTO', conta: 'Banco Principal LES', conciliado: comprovanteJaValidado, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Vendas->Fin)`, criadoEm: agora }); db.financeiro.lancamentos.unshift({ id: `FIN-SAL-${prop.numero}`, natureza: 'RECEITA', categoria: 'Venda de Equipamentos / Sistemas', desc: `Saldo 70% na Entrega — Cotação Nº ${prop.numero}${sufixoPagto}`, cc: 'Comercial / Vendas', entidade: nomeCli, doc: docRef, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: valorSaldo70, impostos: 0, ajuste: 0, liquido: valorSaldo70, status: 'PREVISTO', conta: 'Banco Principal LES', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Vendas->Fin)`, criadoEm: agora }); if (valorComissao4 > 0) { db.financeiro.lancamentos.unshift({ id: `FIN-COM-${prop.numero}`, natureza: 'DESPESA_OP', categoria: 'Comissões Comerciais (4%)', desc: `Comissão Comercial 4% (${vendedorNome}) — Cotação Nº ${prop.numero} (4% s/ Conta ${fmtBRL(valorTotal)})`, cc: 'Comercial / Vendas', entidade: `Vendedor: ${vendedorNome} (Comissão 4%)`, doc: docRef, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: valorComissao4, impostos: 0, ajuste: 0, liquido: valorComissao4, status: prop.comissaoStatus === 'PAGA' ? 'REALIZADO' : 'PREVISTO', conta: 'Banco Principal LES', conciliado: prop.comissaoStatus === 'PAGA', estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Comissão 4%)`, criadoEm: agora }); } if (custoFreteImp > 0) { db.financeiro.lancamentos.unshift({ id: `FIN-IMP-${prop.numero}`, natureza: 'CPV_CMV', categoria: 'Importação / Compras Internacionais', desc: `Provisão Frete + Taxas de Importação (${numPO})`, cc: 'Logística & Importação', entidade: prop.fabricante || 'Fornecedor Internacional', doc: docRef, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: custoFreteImp, impostos: 0, ajuste: 0, liquido: custoFreteImp, status: 'PREVISTO', conta: 'Banco Principal LES', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto PO->Fin)`, criadoEm: agora }); } } let alvoDossieAtual = null; function abrirModalDossiePorProposta(numProposta) { const prop = db.propostas.find(p => String(p.numero) === String(numProposta)); if (!prop) return; if (prop.poVinculado && db.pos.some(po => po.numero === prop.poVinculado)) { abrirModalAnexosPO(prop.poVinculado); return; } alvoDossieAtual = { tipo: 'PROPOSTA', id: String(numProposta) }; poSelecionadoModal = null; if (!prop.dossieVendas) { prop.dossieVendas = { proposta: { checked: true, nome: `Proposta_LES_${prop.numero}.pdf`, info: `Cotação Nº ${prop.numero}`, url: 'SISTEMA', enviadoPor: prop.criadoPor, data: prop.criadoEm }, cotacaoDRS: null, comprovantePagto: null }; } document.getElementById('modal-po-titulo').textContent = `Dossiê Comercial da Cotação Nº ${prop.numero}`; renderSlotsModalDossieUnificado(prop.dossieVendas, prop.numero); document.getElementById('modal-po-anexos').classList.remove('hidden'); } function abrirModalAnexosPO(numPO) { poSelecionadoModal = numPO; const po = db.pos.find(p => String(p.numero) === String(numPO)); if (!po) return; alvoDossieAtual = { tipo: 'PO', id: String(numPO) }; if (!po.anexosPO) po.anexosPO = { proposta: null, cotacaoDRS: null, comprovantePagto: null }; document.getElementById('modal-po-titulo').textContent = `Dossiê Comercial do Pedido ${po.numero} (Ref. Cotação ${po.refVenda})`; renderSlotsModalDossieUnificado(po.anexosPO, po.refVenda); document.getElementById('modal-po-anexos').classList.remove('hidden'); } function fecharModalAnexosPO() { poSelecionadoModal = null; alvoDossieAtual = null; document.getElementById('modal-po-anexos').classList.add('hidden'); renderAll(); } function renderSlotsModalDossieUnificado(anexosObj, refCotacao) { const a = anexosObj || {}; const podeEditarVendas = usuarioPodeInserirDossieVendas(); const slots = [ { chave: 'proposta', titulo: '1. Proposta Comercial Assinada (PDF/Imagem)', responsavelLabel: 'Perfil Comercial / Vendas', placeholderInfo: 'Ex: Termo de Aceite assinado pelo cliente' }, { chave: 'cotacaoDRS', titulo: '2. Cotação Oficial do Fabricante / DRS', responsavelLabel: 'Exclusivo Perfil de Vendas / Comercial', placeholderInfo: 'Informe Nº da Cotação do Fabricante, Moeda/Valor e Condição (Ex: DRS-QUO-890 • EUR 4.200)' }, { chave: 'comprovantePagto', titulo: '3. Comprovante de Pagamento do Cliente (Sinal 30%)', responsavelLabel: 'Exclusivo Perfil de Vendas / Comercial', placeholderInfo: 'Informe Data, Forma de Pagto, Valor Pago e Nº Transação (Ex: PIX R$ 14.500 em 20/10)' } ]; document.getElementById('modal-po-slots').innerHTML = slots.map(s => { const atual = a[s.chave]; const infoAtual = atual?.info || ''; return ` <div class="p-4 rounded-xl border-2 ${atual ? 'bg-emerald-50/70 border-emerald-500' : 'bg-zinc-50 border-zinc-200'} space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2"> <div> <span class="font-extrabold text-zinc-950 text-xs block">${atual ? '✅ ' : '⏳ '}${s.titulo}</span> <span class="text-[10px] font-bold uppercase tracking-wider text-emerald-700"><i class="fa-solid fa-user-tag mr-1"></i>Responsável: ${s.responsavelLabel}</span> </div> ${atual ? `<span class="px-2.5 py-1 bg-emerald-600 text-white rounded-full text-[10px] font-bold"><i class="fa-solid fa-check mr-1"></i>VALIDADO — ${atual.nome} (${atual.enviadoPor})</span>` : `<span class="px-2.5 py-1 bg-amber-100 text-amber-800 rounded-full text-[10px] font-bold">Pendente (Vendas)</span>`} </div> ${atual && atual.info ? ` <div class="bg-white/90 border border-emerald-200 rounded-lg px-3 py-2 text-xs text-zinc-800"> <b>Informação registrada por Vendas:</b> ${atual.info} </div> ` : ''} ${podeEditarVendas ? ` <div class="grid grid-cols-1 md:grid-cols-12 gap-2 items-center pt-1"> <input type="text" id="input-info-slot-${s.chave}" value="${infoAtual}" placeholder="${s.placeholderInfo}" class="md:col-span-7 border border-zinc-300 rounded-lg px-3 py-1.5 bg-white text-xs"> <div class="md:col-span-5 flex flex-wrap gap-1.5 justify-end"> <label class="cursor-pointer bg-zinc-900 hover:bg-zinc-700 text-white px-3 py-1.5 rounded-lg text-[11px] font-semibold"> <i class="fa-solid fa-upload mr-1"></i> Anexar PDF/Img <input type="file" accept=".pdf,image/*" onchange="uploadAnexoSlotUnificado('${s.chave}', event)" class="hidden"> </label> <button type="button" onclick="salvarInfoECheckSlotVendas('${s.chave}')" class="bg-emerald-600 hover:bg-emerald-700 text-white px-3 py-1.5 rounded-lg text-[11px] font-bold"> <i class="fa-solid fa-check mr-1"></i> Salvar & Validar </button> </div> </div> ` : ` <div class="text-[11px] text-zinc-500 italic bg-zinc-100 px-3 py-2 rounded-lg"> <i class="fa-solid fa-lock mr-1"></i> A inserção de dados e arquivos deste item é realizada pelo <b>Perfil de Vendas / Comercial</b>. </div> `} <div class="flex items-center justify-end gap-2"> ${atual && atual.url && atual.url.startsWith('data:') ? `<a href="${atual.url}" download="${atual.nome}" class="px-3 py-1 bg-sky-600 text-white rounded-lg text-[11px] font-semibold"><i class="fa-solid fa-download mr-1"></i>Baixar Arquivo Anexado</a>` : atual && atual.url === 'SISTEMA' ? `<button type="button" onclick="gerarPDFPropostaExecutiva('${refCotacao}')" class="px-3 py-1 bg-zinc-800 text-white rounded-lg text-[11px] font-semibold">Ver PDF da Proposta</button>` : ''} </div> </div> `; }).join(''); } function aplicarAtualizacaoDossieVendas(chaveSlot, nomeArquivo, dataUrl, infoTexto) { if (!exigirPermissao(['vendedor', 'diretor_drones', 'diretoria'], 'Inserir Cotação do Fabricante ou Comprovante de Pagamento (Vendas / Diretoria)')) return; if (!alvoDossieAtual) return; const agora = carimboHoraAtual(); const registroSlot = { checked: true, nome: nomeArquivo || 'Validado_Vendas.pdf', info: infoTexto || '', url: dataUrl || 'VALIDADO', enviadoPor: sessaoAtual.nome, data: agora }; let refCotacao = ''; if (alvoDossieAtual.tipo === 'PO') { const po = db.pos.find(x => String(x.numero) === String(alvoDossieAtual.id)); if (!po) return; refCotacao = po.refVenda; if (!po.anexosPO) po.anexosPO = { proposta: null, cotacaoDRS: null, comprovantePagto: null }; if (po.anexosPO[chaveSlot] && (!dataUrl || dataUrl === 'VALIDADO') && po.anexosPO[chaveSlot].url?.startsWith('data:')) { registroSlot.url = po.anexosPO[chaveSlot].url; registroSlot.nome = po.anexosPO[chaveSlot].nome; } po.anexosPO[chaveSlot] = registroSlot; po.modificadoPor = sessaoAtual.nome; po.modificadoEm = agora; const propVinc = db.propostas.find(p => String(p.numero) === String(po.refVenda)); if (propVinc) { propVinc.dossieVendas = propVinc.dossieVendas || {}; propVinc.dossieVendas[chaveSlot] = registroSlot; if (chaveSlot === 'cotacaoDRS' && infoTexto) propVinc.cotacaoFabRef = infoTexto; if (chaveSlot === 'comprovantePagto' && infoTexto) propVinc.comprovanteRef = infoTexto; } if (po.anexosPO.proposta && po.anexosPO.cotacaoDRS && po.anexosPO.comprovantePagto && po.status !== 'Cancelado') { if (po.tracking && po.tracking.etapaCodigo === '1_PEDIDO_RECEBIDO') { po.tracking.etapaCodigo = '2_PO_EMITIDO'; po.tracking.processoAtual = 'Dossiê Comercial Completo (Cotação Fab. + Comprovante OK) — P.O. Liberado'; po.tracking.historico.unshift({ dataHora: agora, etapa: '2. P.O. Emitido ao Fabricante Internacional', local: po.tracking.localAtual || 'Graz, Áustria', processo: 'Dossiê comercial validado por Vendas', obs: infoTexto || '', usuario: sessaoAtual.nome }); } po.status = '2. P.O. Emitido ao Fabricante Internacional'; } renderSlotsModalDossieUnificado(po.anexosPO, po.refVenda); } else { const prop = db.propostas.find(p => String(p.numero) === String(alvoDossieAtual.id)); if (!prop) return; refCotacao = prop.numero; prop.dossieVendas = prop.dossieVendas || {}; if (prop.dossieVendas[chaveSlot] && (!dataUrl || dataUrl === 'VALIDADO') && prop.dossieVendas[chaveSlot].url?.startsWith('data:')) { registroSlot.url = prop.dossieVendas[chaveSlot].url; registroSlot.nome = prop.dossieVendas[chaveSlot].nome; } prop.dossieVendas[chaveSlot] = registroSlot; if (chaveSlot === 'cotacaoDRS' && infoTexto) prop.cotacaoFabRef = infoTexto; if (chaveSlot === 'comprovantePagto' && infoTexto) prop.comprovanteRef = infoTexto; prop.modificadoPor = sessaoAtual.nome; prop.modificadoEm = agora; renderSlotsModalDossieUnificado(prop.dossieVendas, prop.numero); } if (chaveSlot === 'comprovantePagto' && refCotacao) { const finSinal = db.financeiro.lancamentos.find(l => l.id === `FIN-SIN-${refCotacao}`); if (finSinal) { finSinal.status = 'REALIZADO'; finSinal.conciliado = true; finSinal.liquidacao = new Date().toISOString().slice(0, 10); finSinal.modificadoPor = `${sessaoAtual.nome} (Comprovante Vendas)`; finSinal.modificadoEm = agora; } } registrarAuditoria('VENDAS', `Inseriu/Validou ${chaveSlot} (${nomeArquivo}${infoTexto ? ' — ' + infoTexto : ''}) no Pedido Ref. ${refCotacao}`, refCotacao); saveDB(); } function salvarInfoECheckSlotVendas(chaveSlot) { const inputInfo = document.getElementById(`input-info-slot-${chaveSlot}`); const infoTexto = inputInfo ? inputInfo.value.trim() : ''; aplicarAtualizacaoDossieVendas(chaveSlot, 'Validado_Comercial.pdf', 'VALIDADO', infoTexto); } function uploadAnexoSlotUnificado(chaveSlot, event) { const file = event.target.files[0]; if (!file) return; const inputInfo = document.getElementById(`input-info-slot-${chaveSlot}`); const infoTexto = inputInfo ? inputInfo.value.trim() : ''; if (file.size > 1500000) { aplicarAtualizacaoDossieVendas(chaveSlot, file.name, 'VALIDADO_ARQUIVO_GRANDE', infoTexto); return; } const reader = new FileReader(); reader.onload = e => { aplicarAtualizacaoDossieVendas(chaveSlot, file.name, e.target.result, infoTexto); }; reader.onerror = () => { aplicarAtualizacaoDossieVendas(chaveSlot, file.name, 'VALIDADO', infoTexto); }; reader.readAsDataURL(file); } function obterDefEtapaTracking(codigo) { return ETAPAS_TRACKING_DEFINICAO.find(e => e.codigo === codigo) || ETAPAS_TRACKING_DEFINICAO[1]; } function abrirModalTrackingPO(numPO) { if (!exigirPermissao(['logistica'], 'Editar Rastreamento de Pedido (Exclusivo Pessoal de Logística)')) return; const po = db.pos.find(p => String(p.numero) === String(numPO)); if (!po) return; garantirEstruturaDB(); document.getElementById('trk-po-numero').value = po.numero; document.getElementById('modal-trk-titulo').textContent = `Rastreamento do Pedido ${po.numero} (Ref. Cotação Nº ${po.refVenda})`; document.getElementById('trk-etapa').value = po.tracking.etapaCodigo || '2_PO_EMITIDO'; document.getElementById('trk-local').value = po.tracking.localAtual || ''; document.getElementById('trk-processo').value = po.tracking.processoAtual || ''; document.getElementById('trk-awb').value = po.tracking.awb || ''; document.getElementById('trk-di').value = po.tracking.diDuimp || ''; document.getElementById('trk-eta').value = po.tracking.eta || po.previsao || ''; document.getElementById('trk-obs').value = ''; renderStepperNoModalTracking(po); document.getElementById('modal-tracking-po').classList.remove('hidden'); } function fecharModalTrackingPO() { document.getElementById('modal-tracking-po').classList.add('hidden'); } function renderStepperNoModalTracking(po) { const etapaAtual = obterDefEtapaTracking(po.tracking?.etapaCodigo); const stepperEl = document.getElementById('modal-trk-stepper'); if (stepperEl) { stepperEl.innerHTML = ` <div class="grid grid-cols-2 md:grid-cols-5 gap-2"> ${ETAPAS_TRACKING_DEFINICAO.map(st => { const concluida = st.ordem < etapaAtual.ordem; const atual = st.ordem === etapaAtual.ordem; return ` <div class="p-2.5 rounded-lg border text-[11px] flex items-center gap-2 ${atual ? 'bg-sky-950 text-white border-sky-700 font-bold shadow' : concluida ? 'bg-emerald-50 border-emerald-300 text-emerald-900 font-semibold' : 'bg-zinc-50 border-zinc-200 text-zinc-400'}"> <span class="w-5 h-5 rounded-full flex items-center justify-center text-[10px] shrink-0 ${atual ? 'bg-sky-400 text-zinc-950 font-black' : concluida ? 'bg-emerald-600 text-white' : 'bg-zinc-200 text-zinc-600'}"> ${concluida ? '✓' : st.ordem} </span> <span class="truncate">${st.curto.replace(/^\d+\.\s*/, '')}</span> </div> `; }).join('')} </div> `; } const histEl = document.getElementById('modal-trk-historico'); const hist = po.tracking?.historico || []; if (histEl) { histEl.innerHTML = hist.map(h => ` <div class="p-2.5 rounded-lg bg-zinc-50 border border-zinc-200 text-xs flex flex-wrap justify-between items-center gap-2"> <div> <span class="font-bold text-zinc-900">${h.etapa}</span> <span class="text-zinc-600 block"><b>Onde:</b> ${h.local} • <b>Processo:</b> ${h.processo} ${h.obs ? `— <i>"${h.obs}"</i>` : ''}</span> </div> <div class="text-right text-[10px] text-zinc-500 font-mono"> ${h.dataHora}<br><b>${h.usuario}</b> </div> </div> `).join(''); } } function salvarAtualizacaoTrackingPO(e) { e.preventDefault(); if (!exigirPermissao(['logistica'], 'Atualizar Status de Rastreamento do Pedido (Exclusivo Logística)')) return; const numPO = document.getElementById('trk-po-numero').value; const po = db.pos.find(p => String(p.numero) === String(numPO)); if (!po) return; const codigoEtapa = document.getElementById('trk-etapa').value; const defEtapa = obterDefEtapaTracking(codigoEtapa); const localAtual = document.getElementById('trk-local').value.trim(); const processoAtual = document.getElementById('trk-processo').value.trim(); const awb = document.getElementById('trk-awb').value.trim(); const diDuimp = document.getElementById('trk-di').value.trim(); const eta = document.getElementById('trk-eta').value; const obs = document.getElementById('trk-obs').value.trim(); const agora = carimboHoraAtual(); po.tracking = po.tracking || { historico: [] }; po.tracking.etapaCodigo = codigoEtapa; po.tracking.localAtual = localAtual; po.tracking.processoAtual = processoAtual; po.tracking.awb = awb; po.tracking.diDuimp = diDuimp; po.tracking.eta = eta; po.tracking.obs = obs; po.tracking.atualizadoPor = sessaoAtual.nome; po.tracking.atualizadoEm = agora; po.tracking.historico = po.tracking.historico || []; po.tracking.historico.unshift({ dataHora: agora, etapa: defEtapa.label, local: localAtual, processo: processoAtual, obs: `${awb ? '[AWB: ' + awb + '] ' : ''}${diDuimp ? '[DI/DUIMP: ' + diDuimp + '] ' : ''}${obs}`.trim(), usuario: sessaoAtual.nome }); po.status = codigoEtapa === '10_ENTREGUE' ? 'Concluído' : defEtapa.label; if (eta) po.previsao = eta; po.modificadoPor = sessaoAtual.nome; po.modificadoEm = agora; const vendaVinc = db.vendas.find(v => v.poGerado === po.numero || String(v.propostaNumero) === String(po.refVenda)); if (vendaVinc) { vendaVinc.status = `${defEtapa.curto} (${localAtual})`; } registrarAuditoria('LOGISTICA', `Atualizou Rastreamento do ${po.numero}: ${defEtapa.curto} — Local: ${localAtual} (${processoAtual})`, po.numero); saveDB(); renderStepperNoModalTracking(po); fecharModalTrackingPO(); } function construirBarraSimplesEntregaHTML(ordemEtapa, cancelado = false) { const marcos = [ { idx: 1, nome: '1. Pedido / P.O.', ativo: ordemEtapa >= 1, concluido: ordemEtapa >= 4 }, { idx: 2, nome: '2. Em Trânsito', ativo: ordemEtapa >= 4, concluido: ordemEtapa >= 6 }, { idx: 3, nome: '3. Desembaraço Brasil', ativo: ordemEtapa >= 6, concluido: ordemEtapa >= 9 }, { idx: 4, nome: '4. Entregue', ativo: ordemEtapa >= 10, concluido: ordemEtapa >= 10 } ]; return ` <div class="grid grid-cols-4 gap-2 pt-1"> ${marcos.map(m => ` <div class="px-2.5 py-1.5 rounded-lg border text-center text-[11px] font-bold ${cancelado ? 'bg-rose-50 border-rose-200 text-rose-700' : m.concluido ? 'bg-emerald-600 text-white border-emerald-600' : m.ativo ? 'bg-sky-600 text-white border-sky-600 shadow-sm' : 'bg-zinc-100 text-zinc-400 border-zinc-200'}"> ${m.concluido ? '✓ ' : ''}${m.nome} </div> `).join('')} </div> `; } function abrirJanelaRastreabilidadeVenda(numCotacao) { const prop = db.propostas.find(p => String(p.numero) === String(numCotacao)); const venda = db.vendas.find(v => String(v.propostaNumero) === String(numCotacao)); const po = db.pos.find(x => String(x.refVenda) === String(numCotacao) || (prop && x.numero === prop.poVinculado) || (venda && x.numero === venda.poGerado)); const cliId = prop ? prop.clienteId : venda ? venda.clienteId : po ? po.clienteId : null; const cli = db.clientes.find(c => Number(c.id) === Number(cliId)) || {}; const trk = po?.tracking || null; const defEtapa = trk ? obterDefEtapaTracking(trk.etapaCodigo) : null; const dossie = po?.anexosPO || prop?.dossieVendas || {}; const vendedorNome = prop?.vendedor || venda?.vendedor || prop?.criadoPor || 'Vendedor LES'; const contaGerada = Number(prop?.total ?? venda?.valorAutorizado ?? 0); const comissao4 = Number((contaGerada * TAXA_COMISSAO_VENDEDOR).toFixed(2)); document.getElementById('modal-rv-titulo').textContent = `Rastreabilidade & Status de Entrega — Cotação Nº ${numCotacao} ${po ? `(${po.numero})` : ''}`; const histHTML = (trk?.historico || []).slice(0, 6).map(h => ` <div class="flex justify-between items-center p-2 rounded-lg bg-zinc-50 border text-[11px]"> <div><b>${h.etapa}</b> — ${h.local} (${h.processo})</div> <span class="font-mono text-[10px] text-zinc-500">${h.dataHora} • ${h.usuario}</span> </div> `).join(''); document.getElementById('modal-rv-conteudo').innerHTML = ` <div class="p-4 rounded-xl bg-zinc-50 border space-y-3"> <div class="flex flex-wrap justify-between items-center gap-2"> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Cliente & Destino</span> <b class="text-sm text-zinc-950">${cli.nome || '-'}</b> <span class="block text-[11px] text-zinc-600">${po?.enderecoEntrega || cli.endereco || '-'}</span> </div> <div class="text-right"> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Status de Entrega Atual</span> <span class="px-3 py-1 rounded-full text-xs font-extrabold ${po ? 'bg-sky-600 text-white' : 'bg-amber-100 text-amber-900'} inline-block mt-0.5"> ${defEtapa ? defEtapa.curto : (prop?.status || 'Aguardando geração de P.O.')} </span> </div> </div> ${defEtapa ? construirBarraSimplesEntregaHTML(defEtapa.ordem, po?.status === 'Cancelado') : ''} <div class="grid grid-cols-1 md:grid-cols-3 gap-3 pt-2 border-t text-xs"> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Onde está o pedido?</span> <b class="text-zinc-900"><i class="fa-solid fa-location-dot text-rose-500 mr-1"></i>${trk?.localAtual || 'Em processamento comercial'}</b> </div> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Previsão de Entrega (ETA)</span> <b class="text-emerald-700">${fmtDataBR(trk?.eta || po?.previsao)}</b> </div> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Rastreio / AWB / DI</span> <span class="font-mono font-bold text-zinc-800">${trk?.awb || 'Aguardando código'} ${trk?.diDuimp ? `• ${trk.diDuimp}` : ''}</span> </div> </div> </div> ${usuarioEhResponsavelPelaVenda(vendedorNome) ? ` <div class="p-3.5 rounded-xl border border-zinc-300 bg-zinc-50 flex flex-wrap justify-between items-center gap-3"> <div> <span class="text-[10px] font-extrabold uppercase text-zinc-500 block">Responsável pela Venda</span> <p class="text-xs text-zinc-900 mt-0.5"><b>${vendedorNome}</b> • Conta da Proposta: <b>${fmtBRL(contaGerada)}</b></p> </div> <div class="text-right"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">Valor do Responsável</span> <span class="font-mono font-black text-base text-emerald-800">${fmtBRL(comissao4)}</span> </div> </div> ` : ''} <div class="grid grid-cols-1 md:grid-cols-2 gap-3"> <div class="p-3.5 rounded-xl border bg-white space-y-1.5"> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Rastreabilidade de Criação e Aprovação</span> <p><b>Criado por:</b> ${prop?.criadoPor || venda?.criadoPor || '-'} (${prop?.criadoEm || '-'})</p> <p><b>Aprovado Diretoria:</b> ${prop?.aprovadoPor || venda?.aprovadoPor || 'Pendente'} ${prop?.aprovadoEm ? `(${prop.aprovadoEm})` : ''}</p> <p><b>P.O. Vinculado:</b> <span class="font-mono font-bold">${po ? po.numero : 'Ainda não emitido'}</span></p> </div> <div class="p-3.5 rounded-xl border bg-white space-y-1.5"> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Status do Dossiê Comercial</span> <p>${dossie.proposta ? '✅' : '⏳'} <b>1. Proposta Comercial</b></p> <p>${dossie.cotacaoDRS ? '✅' : '⏳'} <b>2. Cotação do Fabricante:</b> ${dossie.cotacaoDRS?.info || 'Pendente'}</p> <p>${dossie.comprovantePagto ? '✅' : '⏳'} <b>3. Comprovante de Pagto:</b> ${dossie.comprovantePagto?.info || 'Pendente'}</p> </div> </div> ${histHTML ? ` <div class="space-y-1.5"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">Últimas Atualizações da Logística</span> <div class="space-y-1.5 max-h-40 overflow-y-auto">${histHTML}</div> </div> ` : ''} `; document.getElementById('modal-status-rastreabilidade-venda').classList.remove('hidden'); } function fecharJanelaRastreabilidadeVenda() { document.getElementById('modal-status-rastreabilidade-venda').classList.add('hidden'); } function renderTrackingCards() { const container = document.getElementById('lista-tracking-cards'); if (!container) return; const termo = normalizarTexto(document.getElementById('filtro-tracking-po')?.value || ''); const posAtivos = db.pos.filter(p => { if (!termo) return true; const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); const textoBusca = `${p.numero} ${p.refVenda} ${cli?.nome || ''} ${p.produto} ${p.tracking?.localAtual || ''} ${p.tracking?.processoAtual || ''} ${p.tracking?.awb || ''} ${p.status}`; return normalizarTexto(textoBusca).includes(termo); }); const totalPOs = db.pos.filter(p => p.status !== 'Cancelado').length; const jaDespachados = db.pos.filter(p => p.status !== 'Cancelado' && obterDefEtapaTracking(p.tracking?.etapaCodigo).ordem >= 4).length; const emDesembaraco = db.pos.filter(p => p.status !== 'Cancelado' && p.tracking?.etapaCodigo === '7_EM_DESEMBARACO').length; const jaDesembaracados = db.pos.filter(p => p.status !== 'Cancelado' && obterDefEtapaTracking(p.tracking?.etapaCodigo).ordem >= 8).length; const entregues = db.pos.filter(p => p.status !== 'Cancelado' && p.tracking?.etapaCodigo === '10_ENTREGUE').length; const kpiEl = document.getElementById('log-kpis-grid'); if (kpiEl) { kpiEl.innerHTML = ` <div class="les-card p-4 border-l-4 border-l-zinc-900"><p class="text-[10px] font-bold uppercase text-zinc-500">Pedidos Ativos</p><p class="text-2xl font-extrabold mt-1">${totalPOs}</p></div> <div class="les-card p-4 border-l-4 border-l-amber-500"><p class="text-[10px] font-bold uppercase text-zinc-500">Em Trânsito / Despachados</p><p class="text-2xl font-extrabold text-amber-700 mt-1">${jaDespachados}</p></div> <div class="les-card p-4 border-l-4 border-l-orange-500"><p class="text-[10px] font-bold uppercase text-zinc-500">Em Desembaraço</p><p class="text-2xl font-extrabold text-orange-600 mt-1">${emDesembaraco}</p></div> <div class="les-card p-4 border-l-4 border-l-teal-600"><p class="text-[10px] font-bold uppercase text-zinc-500">Liberados / Brasil</p><p class="text-2xl font-extrabold text-teal-700 mt-1">${jaDesembaracados}</p></div> <div class="les-card p-4 border-l-4 border-l-emerald-600"><p class="text-[10px] font-bold uppercase text-zinc-500">Entregues</p><p class="text-2xl font-extrabold text-emerald-700 mt-1">${entregues}</p></div> `; } if (posAtivos.length === 0) { container.innerHTML = `<div class="p-6 text-center text-zinc-400 text-xs italic border rounded-xl bg-zinc-50">Nenhum pedido P.O. ativo para rastreamento.</div>`; return; } container.innerHTML = posAtivos.map(p => { const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); const trk = p.tracking || {}; const defEtapa = obterDefEtapaTracking(trk.etapaCodigo); const cancelado = p.status === 'Cancelado'; return ` <div class="p-4 rounded-xl border ${cancelado ? 'bg-rose-50/40 border-rose-200 opacity-75' : 'bg-white border-zinc-200 shadow-sm'} space-y-3"> <div class="flex flex-wrap justify-between items-center gap-3"> <div> <div class="flex flex-wrap items-center gap-2"> <span class="font-mono font-black text-xs text-white bg-zinc-950 px-2.5 py-1 rounded-lg">${p.numero}</span> <span class="text-xs font-extrabold text-zinc-900">${cli ? cli.nome : '-'}</span> <span class="text-[11px] text-zinc-500">• ${p.produto}</span> </div> </div> <div class="flex items-center gap-2"> <span class="px-2.5 py-1 rounded-full text-[10px] font-bold bg-sky-100 text-sky-900">${defEtapa.curto}</span> ${usuarioPodeAtualizarLogistica() ? ` <button type="button" onclick="abrirModalTrackingPO('${p.numero}')" class="bg-sky-600 hover:bg-sky-700 text-white px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm transition"> <i class="fa-solid fa-pen-to-square mr-1"></i> Atualizar Entrega </button> ` : `<span class="px-2.5 py-1 bg-zinc-100 text-zinc-500 rounded text-[10px] font-semibold">Edição: Logística</span>`} </div> </div> ${construirBarraSimplesEntregaHTML(defEtapa.ordem, cancelado)} <div class="grid grid-cols-1 md:grid-cols-3 gap-2 bg-zinc-50 p-2.5 rounded-lg border text-xs"> <div><span class="text-[10px] font-bold uppercase text-zinc-400 block">Onde está:</span><b><i class="fa-solid fa-location-dot text-rose-500 mr-1"></i>${trk.localAtual || '-'}</b> (${trk.processoAtual || '-'})</div> <div><span class="text-[10px] font-bold uppercase text-zinc-400 block">Código Rastreio / AWB:</span><span class="font-mono">${trk.awb || 'S/ Rastreio'} ${trk.diDuimp ? `• ${trk.diDuimp}` : ''}</span></div> <div><span class="text-[10px] font-bold uppercase text-zinc-400 block">Previsão de Entrega (ETA):</span><b>${fmtDataBR(trk.eta || p.previsao)}</b></div> </div> </div> `; }).join(''); } // ========================================================= // MÓDULO DE ESTOQUE & AUDITORIA CONTÁBIL (INTERLIGADO AO FINANCEIRO) // ========================================================= function aoSelecionarPNEstoque() { const pnSel = document.getElementById('est-pn')?.value; const pnObj = db.pns.find(p => p.pn === pnSel); if (pnObj) { const descEl = document.getElementById('est-desc'); if (descEl) descEl.value = pnObj.desc; calcularValidadeAutomaticaEstoque(); } } function calcularValidadeAutomaticaEstoque() { const pnSel = document.getElementById('est-pn')?.value; const dataEnt = document.getElementById('est-entrada')?.value; if (!dataEnt) return; const pnObj = db.pns.find(p => p.pn === pnSel); const meses = pnObj ? (Number(pnObj.shelf) || 24) : 24; const valEl = document.getElementById('est-validade'); if (valEl) valEl.value = adicionarMesesData(dataEnt, meses); } function recalcularPreviewContabilEstoque() { const qtd = parseFloat(document.getElementById('est-qtd')?.value) || 0; const unit = parseFloat(document.getElementById('est-custo-unit')?.value) || 0; const el = document.getElementById('est-valor-total-preview'); if (el) el.value = fmtBRL(qtd * unit); } function calcularValorContabilTotalEstoque() { return (db.estoque || []).reduce((acc, item) => acc + (Number(item.qtd || 0) * Number(item.custoUnit || 0)), 0); } function salvarItemEstoque(e) { e.preventDefault(); garantirEstruturaDB(); const editId = document.getElementById('est-edit-id').value.trim(); const pn = document.getElementById('est-pn').value; const desc = document.getElementById('est-desc').value.trim(); const categoria = document.getElementById('est-categoria').value; const qtd = parseInt(document.getElementById('est-qtd').value) || 0; const qtdMin = parseInt(document.getElementById('est-qtd-min').value) || 0; const lote = document.getElementById('est-lote').value.trim(); const sn = document.getElementById('est-sn').value.trim(); const entrada = document.getElementById('est-entrada').value || new Date().toISOString().slice(0, 10); const validade = document.getElementById('est-validade').value; const local = document.getElementById('est-local').value.trim(); const docOrigem = document.getElementById('est-doc-origem').value.trim() || 'ENTRADA-ESTOQUE'; const custoUnit = parseFloat(document.getElementById('est-custo-unit').value) || 0; const valorTotalContabil = Number((qtd * custoUnit).toFixed(2)); const agora = carimboHoraAtual(); if (editId) { const idx = db.estoque.findIndex(x => x.id === editId); if (idx >= 0) { const ant = db.estoque[idx]; const valorAntigo = Number(ant.qtd || 0) * Number(ant.custoUnit || 0); db.estoque[idx] = { ...ant, pn, desc, categoria, qtd, qtdMin, lote, sn, entrada, validade, local, docOrigem, custoUnit, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; const difContabil = valorTotalContabil - valorAntigo; if (Math.abs(difContabil) > 0.01) { registrarReflexoContabilEstoque( editId, difContabil > 0 ? 'COMPRA_ESTOQUE' : 'BAIXA_ESTOQUE', Math.abs(difContabil), `Ajuste Contábil de Inventário (${pn} ${lote ? '• Lote ' + lote : ''})`, docOrigem, entrada ); } registrarAuditoria('ESTOQUE', `Editou item de estoque ${editId} (${pn}) — Qtd: ${qtd} | Valor Contábil: ${fmtBRL(valorTotalContabil)}`, editId); cancelarEdicaoEstoque(); saveDB(); return; } } const itemMesmoPN = db.estoque.find(x => normalizarTexto(x.pn) === normalizarTexto(pn) && normalizarTexto(x.lote || '') === normalizarTexto(lote || '')); let novoId = 'EST-' + Date.now().toString().slice(-5); if (itemMesmoPN) { novoId = itemMesmoPN.id; itemMesmoPN.qtd = Number(itemMesmoPN.qtd || 0) + qtd; if (sn && !String(itemMesmoPN.sn || '').includes(sn)) { itemMesmoPN.sn = itemMesmoPN.sn ? `${itemMesmoPN.sn} / ${sn}` : sn; } if (custoUnit > 0) itemMesmoPN.custoUnit = custoUnit; if (local) itemMesmoPN.local = local; if (validade) itemMesmoPN.validade = validade; itemMesmoPN.modificadoPor = sessaoAtual.nome; itemMesmoPN.modificadoEm = agora; } else { const novoItem = { id: novoId, pn, desc, categoria, qtd, qtdMin, lote, sn, entrada, validade, local, docOrigem, custoUnit, criadoPor: sessaoAtual.nome, criadoEm: agora }; db.estoque.unshift(novoItem); } db.movimentacoesEstoque.unshift({ id: 'MOV-' + Date.now().toString().slice(-5), itemId: novoId, pn, lote: lote || '-', sn: sn || '-', tipo: 'ENTRADA_CADASTRO', qtd, valorImpacto: valorTotalContabil, motivo: `Cadastro Inicial / Entrada (${docOrigem})`, data: entrada, usuario: sessaoAtual.nome, dataHora: agora }); if (valorTotalContabil > 0) { registrarReflexoContabilEstoque( novoId, 'COMPRA_ESTOQUE', valorTotalContabil, `Entrada em Estoque ${pn} (${qtd}x)${lote ? ' • Lote: ' + lote : ''}${sn ? ' • S.N.: ' + sn : ''}`, `${novoId} / ${docOrigem}`, entrada ); } registrarAuditoria('ESTOQUE', `Cadastrou item em estoque ${novoId} (${pn} • Qtd: ${qtd} • Contábil: ${fmtBRL(valorTotalContabil)})`, novoId); cancelarEdicaoEstoque(); saveDB(); } function registrarReflexoContabilEstoque(refEstoqueId, naturezaFin, valor, descricao, docRef, dataComp) { const hojeISO = dataComp || new Date().toISOString().slice(0, 10); db.financeiro.lancamentos.unshift({ id: `FIN-${refEstoqueId}-${Date.now().toString().slice(-3)}`, natureza: naturezaFin, categoria: 'Estoque & Almoxarifado (Ativo 1.1.03)', desc: descricao, cc: 'Estoque & Almoxarifado', entidade: 'Almoxarifado Central LES Aviation', doc: docRef, competencia: hojeISO, vencimento: hojeISO, bruto: valor, impostos: 0, ajuste: 0, liquido: valor, status: 'CONTABIL_ESTOQUE', conta: '1.1.03 - Estoques Aeronáuticos', conciliado: true, estornado: false, criadoPor: `${sessaoAtual.nome} (Auto Estoque->Contábil)`, criadoEm: carimboHoraAtual() }); } function registrarMovimentacaoEstoque(e) { e.preventDefault(); const itemId = document.getElementById('mov-item-id').value; const tipo = document.getElementById('mov-tipo').value; const qtdMov = parseInt(document.getElementById('mov-qtd').value) || 0; const motivo = document.getElementById('mov-motivo').value.trim(); const dataMov = document.getElementById('mov-data').value || new Date().toISOString().slice(0, 10); const item = db.estoque.find(x => x.id === itemId); if (!item || qtdMov <= 0) return; const ehSaida = ['SAIDA_VENDA_CPV', 'SAIDA_MRO', 'AJUSTE_PERDA'].includes(tipo); if (ehSaida && item.qtd < qtdMov) { alert(`Saldo insuficiente em estoque! O item ${item.pn} possui apenas ${item.qtd} unidade(s) disponível(is).`); return; } item.qtd = ehSaida ? (item.qtd - qtdMov) : (item.qtd + qtdMov); const valorImpacto = Number((qtdMov * Number(item.custoUnit || 0)).toFixed(2)); const agora = carimboHoraAtual(); item.modificadoPor = sessaoAtual.nome; item.modificadoEm = agora; const movId = 'MOV-' + Date.now().toString().slice(-5); db.movimentacoesEstoque.unshift({ id: movId, itemId: item.id, pn: item.pn, lote: item.lote || '-', sn: item.sn || '-', tipo, qtd: ehSaida ? -qtdMov : qtdMov, valorImpacto: ehSaida ? -valorImpacto : valorImpacto, motivo, data: dataMov, usuario: sessaoAtual.nome, dataHora: agora }); if (valorImpacto > 0) { let naturezaContabil = 'COMPRA_ESTOQUE'; if (tipo === 'SAIDA_VENDA_CPV' || tipo === 'SAIDA_MRO') naturezaContabil = 'CPV_CMV'; else if (tipo === 'AJUSTE_PERDA') naturezaContabil = 'DESPESA_OP'; registrarReflexoContabilEstoque( movId, naturezaContabil, valorImpacto, `Mov. Estoque [${tipo}] ${qtdMov}x ${item.pn} — ${motivo}`, `${movId} / ${item.id}`, dataMov ); } registrarAuditoria('ESTOQUE', `Movimentou estoque (${tipo}: ${qtdMov}x ${item.pn}) — Impacto Contábil: ${fmtBRL(valorImpacto)}`, item.id); e.target.reset(); saveDB(); } function editarItemEstoque(id) { const it = db.estoque.find(x => x.id === id); if (!it) return; document.getElementById('est-edit-id').value = it.id; document.getElementById('est-pn').value = it.pn; document.getElementById('est-desc').value = it.desc; document.getElementById('est-categoria').value = it.categoria; document.getElementById('est-qtd').value = it.qtd; document.getElementById('est-qtd-min').value = it.qtdMin || 1; document.getElementById('est-lote').value = it.lote || ''; document.getElementById('est-sn').value = it.sn || ''; document.getElementById('est-entrada').value = it.entrada || ''; document.getElementById('est-validade').value = it.validade || ''; document.getElementById('est-local').value = it.local || ''; document.getElementById('est-doc-origem').value = it.docOrigem || ''; document.getElementById('est-custo-unit').value = it.custoUnit || 0; recalcularPreviewContabilEstoque(); document.getElementById('est-form-titulo').textContent = `Editando Item de Estoque ${it.id} (${it.pn})`; document.getElementById('btn-submit-estoque').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5"></i> Salvar Alterações & Atualizar Contábil`; document.getElementById('btn-cancelar-edicao-est').classList.remove('hidden'); document.getElementById('form-estoque').scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoEstoque() { const form = document.getElementById('form-estoque'); if (form) form.reset(); document.getElementById('est-edit-id').value = ''; document.getElementById('est-form-titulo').textContent = 'Cadastrar / Editar Item em Estoque (P/N, Quantidade, Lote, Validade e S.N.)'; document.getElementById('btn-submit-estoque').innerHTML = `<i class="fa-solid fa-boxes-packing mr-1.5"></i> Salvar no Estoque & Contabilizar no Balanço`; document.getElementById('btn-cancelar-edicao-est').classList.add('hidden'); aoSelecionarPNEstoque(); recalcularPreviewContabilEstoque(); } function removerItemEstoque(id) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Excluir Item de Estoque (Diretoria Drones / Holding)')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${id}`); db.estoque = db.estoque.filter(x => x.id !== id); registrarAuditoria('ESTOQUE', `Removeu item de estoque ${id}`, id); saveDB(); } function renderEstoque() { const itens = db.estoque || []; const totalUnidades = itens.reduce((a, b) => a + Number(b.qtd || 0), 0); const valorContabilTotal = calcularValorContabilTotalEstoque(); const alertasValidade = itens.filter(i => i.validade && diasRestantes(i.validade) <= 60).length; const abaixoMinimo = itens.filter(i => Number(i.qtd || 0) <= Number(i.qtdMin || 0)).length; const kpiGrid = document.getElementById('est-kpis-grid'); if (kpiGrid) { kpiGrid.innerHTML = ` <div class="les-card p-4 border-l-4 border-l-zinc-950"> <p class="text-[10px] font-bold uppercase text-zinc-500">SKUs / Lotes Cadastrados</p> <p class="text-2xl font-extrabold text-zinc-950 mt-1">${itens.length}</p> <p class="text-[11px] text-zinc-400">Vinculados à Base de P/N</p> </div> <div class="les-card p-4 border-l-4 border-l-sky-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Total de Peças / Unidades</p> <p class="text-2xl font-extrabold text-sky-700 mt-1">${totalUnidades}</p> <p class="text-[11px] text-zinc-400">Disponíveis fisicamente</p> </div> <div class="les-card p-4 border-l-4 border-l-emerald-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Valor Contábil em Estoque</p> <p class="text-xl font-extrabold text-emerald-700 mt-1">${fmtBRL(valorContabilTotal)}</p> <p class="text-[11px] text-zinc-400">Conta 1.1.03 (Ativo Circulante)</p> </div> <div class="les-card p-4 border-l-4 border-l-amber-500"> <p class="text-[10px] font-bold uppercase text-zinc-500">Alerta Validade / Shelf (≤60d)</p> <p class="text-2xl font-extrabold text-amber-600 mt-1">${alertasValidade}</p> <p class="text-[11px] text-zinc-400">Monitoramento de lotes</p> </div> <div class="les-card p-4 border-l-4 border-l-rose-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Estoque Mínimo / Crítico</p> <p class="text-2xl font-extrabold text-rose-600 mt-1">${abaixoMinimo}</p> <p class="text-[11px] text-zinc-400">Reposição recomendada</p> </div> `; } const selMov = document.getElementById('mov-item-id'); if (selMov) { selMov.innerHTML = itens.length > 0 ? itens.map(i => `<option value="${i.id}">${i.pn} — Lote: ${i.lote || 'S/L'} | S.N.: ${i.sn || 'S/N'} (Saldo: ${i.qtd} ea)</option>`).join('') : `<option value="">-- Nenhum item cadastrado no estoque --</option>`; } const termo = normalizarTexto(document.getElementById('filtro-estoque')?.value || ''); const filtrados = itens.filter(i => !termo || normalizarTexto(`${i.id} ${i.pn} ${i.desc} ${i.lote} ${i.sn} ${i.local}`).includes(termo)); const tbody = document.getElementById('tabela-estoque'); if (tbody) { tbody.innerHTML = filtrados.length === 0 ? `<tr><td colspan="8" class="p-4 text-center text-zinc-400 italic">Nenhum item cadastrado no estoque.</td></tr>` : filtrados.map(i => { const totalItem = Number(i.qtd || 0) * Number(i.custoUnit || 0); const diasVal = i.validade ? diasRestantes(i.validade) : 9999; const badgeVal = !i.validade ? `<span class="text-zinc-400">N/A</span>` : diasVal < 0 ? `<span class="px-2 py-0.5 rounded bg-rose-100 text-rose-800 font-bold text-[10px]">VENCIDO (${fmtDataBR(i.validade)})</span>` : diasVal <= 60 ? `<span class="px-2 py-0.5 rounded bg-amber-100 text-amber-800 font-bold text-[10px]">Vence em ${diasVal}d (${fmtDataBR(i.validade)})</span>` : `<span class="px-2 py-0.5 rounded bg-emerald-50 text-emerald-800 font-semibold text-[10px]">${fmtDataBR(i.validade)} (${diasVal}d)</span>`; return ` <tr class="hover:bg-zinc-50"> <td class="p-3 font-mono font-bold text-zinc-950">${i.pn}<span class="block text-[10px] font-normal text-zinc-400">${i.id} • Doc: ${i.docOrigem || '-'}</span></td> <td class="p-3 font-semibold">${i.desc}<span class="block text-[10px] font-normal text-zinc-500">${i.categoria}</span></td> <td class="p-3 font-mono text-[11px]"> <div><b>Lote:</b> ${i.lote || '—'}</div> <div><b>S.N.:</b> ${i.sn || '—'}</div> </td> <td class="p-3">${badgeVal}<span class="block text-[10px] text-zinc-400 mt-0.5">Entrada: ${fmtDataBR(i.entrada)}</span></td> <td class="p-3 text-[11px]">${i.local}</td> <td class="p-3 text-center whitespace-nowrap"> <span class="inline-flex items-center justify-center whitespace-nowrap px-2.5 py-1 rounded-lg font-extrabold text-xs ${Number(i.qtd) <= Number(i.qtdMin) ? 'bg-rose-100 text-rose-800' : 'bg-zinc-900 text-white'}">${i.qtd} ea</span> <span class="block text-[10px] text-zinc-400 mt-0.5 whitespace-nowrap">Mín: ${i.qtdMin || 0} ea</span> </td> <td class="p-3 text-right font-bold">${fmtBRL(totalItem)}<span class="block text-[10px] font-normal text-zinc-500">Unit: ${fmtBRL(i.custoUnit)}</span></td> <td class="p-3 text-right space-x-1"> <button type="button" onclick="editarItemEstoque('${i.id}')" class="px-2.5 py-1 bg-sky-600 text-white rounded-lg text-[10px] font-semibold">Editar</button> ${usuarioEhDiretorDrones() ? `<button type="button" onclick="removerItemEstoque('${i.id}')" class="px-2 py-1 bg-rose-100 text-rose-700 rounded-lg text-[10px] font-semibold">Excluir</button>` : ''} </td> </tr> `; }).join(''); } const tbodyKardex = document.getElementById('tabela-kardex'); if (tbodyKardex) { tbodyKardex.innerHTML = (db.movimentacoesEstoque || []).map(m => ` <tr> <td class="p-3 font-mono text-[11px]">${fmtDataBR(m.data)}<span class="block text-[10px] text-zinc-400">${m.id}</span></td> <td class="p-3 font-mono font-bold">${m.pn}<span class="block text-[10px] font-normal text-zinc-500">Lote: ${m.lote} | SN: ${m.sn}</span></td> <td class="p-3 font-semibold">${m.tipo}</td> <td class="p-3 text-center font-bold ${m.qtd >= 0 ? 'text-emerald-700' : 'text-rose-700'}">${m.qtd >= 0 ? '+' + m.qtd : m.qtd}</td> <td class="p-3 font-bold ${m.valorImpacto >= 0 ? 'text-emerald-700' : 'text-rose-700'}">${fmtBRL(m.valorImpacto)}</td> <td class="p-3">${m.motivo}</td> <td class="p-3 text-[11px]">${m.usuario}<span class="block text-[10px] text-zinc-400">${m.dataHora}</span></td> </tr> `).join(''); } } function gerarPDFAuditoriaEstoque() { const itens = db.estoque || []; const totalUnidades = itens.reduce((a, b) => a + Number(b.qtd || 0), 0); const valorContabilTotal = calcularValorContabilTotalEstoque(); const agoraStr = carimboHoraAtual(); const linhasHTML = itens.map((it, idx) => { const sub = Number(it.qtd || 0) * Number(it.custoUnit || 0); return ` <tr> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:center; font-family:monospace;">${String(idx + 1).padStart(2, '0')}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; font-family:monospace; font-weight:700;">${it.pn}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8;">${it.desc}<br><span style="font-size:10px; color:#52525b;">Local: ${it.local}</span></td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; font-family:monospace; font-size:11px;">Lote: ${it.lote || '—'}<br>S.N.: ${it.sn || '—'}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:center;">${it.validade ? fmtDataBR(it.validade) : 'N/A'}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:center; font-weight:800;">${it.qtd}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:center; font-family:monospace; color:#52525b;">[ ]</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right;">${fmtBRL(it.custoUnit)}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right; font-weight:700;">${fmtBRL(sub)}</td> </tr> `; }).join(''); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Relatório de Auditoria Física e Contábil de Estoque — LES AVIATION</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #f4f4f5; margin: 0; padding: 20px; } .page { width: 210mm; min-height: 297mm; background: #ffffff; margin: 0 auto; padding: 15mm 16mm; box-shadow: 0 10px 25px rgba(0,0,0,0.08); display: flex; flex-direction: column; justify-content: space-between; } .sec-badge { display: inline-flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 8px; } .sec-num { background: #09090b; color: #fff; padding: 2px 7px; border-radius: 3px; font-size: 10px; } .box { border: 1px solid #d4d4d8; border-radius: 6px; padding: 12px 14px; background: #fafafa; } table { width: 100%; border-collapse: collapse; font-size: 11px; } th { background: #09090b; color: #ffffff; text-transform: uppercase; font-size: 9.5px; padding: 8px 10px; text-align: left; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES AVIATION</b> • Relatório Oficial de Auditoria Física e Contábil de Estoque</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:700; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir Relatório de Auditoria</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:16px;"> ${obterLogoHTMLDocumento('ALOJA BRASIL COMERCIAL LTDA • CNPJ 48.657.151/0001-43')} <div style="text-align:right;"> <div style="font-size:16px; font-weight:800; text-transform:uppercase;">RELATÓRIO DE AUDITORIA FÍSICA E CONTÁBIL DE ESTOQUE</div> <div style="font-size:11px; color:#3f3f46; margin-top:4px;"> EMISSÃO: <b>${agoraStr}</b> | EMITIDO POR: <b>${sessaoAtual.nome || 'Sistema'}</b> </div> </div> </div> <div style="margin-bottom:16px;"> <div class="sec-badge"><span class="sec-num">01</span> POSIÇÃO CONSOLIDADA E SALDO CONTÁBIL (CONTA 1.1.03 - ATIVO CIRCULANTE)</div> <div class="box" style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px; font-size:12px;"> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">TOTAL DE LOTES / SKUs ATIVOS</div> <div style="font-size:16px; font-weight:800; margin-top:2px;">${itens.length} itens</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">QUANTIDADE TOTAL DE UNIDADES</div> <div style="font-size:16px; font-weight:800; margin-top:2px;">${totalUnidades} unidades</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">VALOR CONTÁBIL TOTAL EM ESTOQUE</div> <div style="font-size:16px; font-weight:900; color:#047857; margin-top:2px;">${fmtBRL(valorContabilTotal)}</div> </div> </div> </div> <div style="margin-bottom:18px;"> <div class="sec-badge"><span class="sec-num">02</span> RELAÇÃO COMPLETA DE ITENS PARA CONFERÊNCIA FÍSICA E AUDITORIA</div> <table style="border:1px solid #d4d4d8;"> <thead> <tr> <th style="text-align:center;">#</th> <th>P/N</th> <th>DESCRIÇÃO & LOCALIZAÇÃO</th> <th>LOTE & S.N.</th> <th style="text-align:center;">VALIDADE</th> <th style="text-align:center;">QTD. SISTEMA</th> <th style="text-align:center;">CONTAGEM FÍSICA</th> <th style="text-align:right;">CUSTO UNIT.</th> <th style="text-align:right;">TOTAL CONTÁBIL</th> </tr> </thead> <tbody>${linhasHTML}</tbody> </table> </div> </div> <div> <div class="sec-badge"><span class="sec-num">03</span> PARECER DE AUDITORIA FÍSICA E CONCILIAÇÃO CONTÁBIL</div> <div class="box" style="font-size:11px; margin-bottom:20px; min-height:55px;"> Declaro que os itens listados acima foram inspecionados fisicamente quanto à quantidade, integridade, número de série (S.N.), lote e prazo de validade (Shelf Life), estando conciliados com o saldo contábil registrado no Balanço Patrimonial Gerencial da LES Aviation. </div> <div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:24px; margin-top:28px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>RESPONSÁVEL ALMOXARIFADO / ESTOQUE</b><br>Conferência Física </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>AUDITOR INTERNO / CONTÁBIL</b><br>Validação de Saldo (Conta 1.1.03) </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>DIRETORIA EXECUTIVA LES AVIATION</b><br>Homologação de Inventário </div> </div> </div> </div> </body> </html> `); win.document.close(); registrarAuditoria('ESTOQUE', `Emitiu Relatório PDF de Auditoria de Estoque (${itens.length} itens • Total Contábil: ${fmtBRL(valorContabilTotal)})`, 'AUDITORIA-PDF'); saveDB(); } function renderGraficosDiretoria() { if (!usuarioEhDiretoria() || typeof Chart === 'undefined') return; const totalProps = db.propostas.length; const qtdAprov = db.propostas.filter(p => p.status === 'APROVADA' || p.status === 'ACEITA').length; const qtdPend = db.propostas.filter(p => p.status === 'PENDENTE_APROVACAO' || p.status === 'EM_ELABORACAO').length; const qtdRecusadas = db.propostas.filter(p => p.status === 'RECUSADA').length; const qtdCanceladas = db.propostas.filter(p => p.status === 'CANCELADA').length; const valAprov = db.propostas.filter(p => p.status === 'APROVADA' || p.status === 'ACEITA').reduce((a, b) => a + Number(b.total || 0), 0); const valPend = db.propostas.filter(p => p.status === 'PENDENTE_APROVACAO' || p.status === 'EM_ELABORACAO').reduce((a, b) => a + Number(b.total || 0), 0); const valRecCanc = db.propostas.filter(p => p.status === 'RECUSADA' || p.status === 'CANCELADA').reduce((a, b) => a + Number(b.total || 0), 0); const taxaAprov = totalProps > 0 ? ((qtdAprov / totalProps) * 100).toFixed(1) + '%' : '0%'; const taxaRecusa = totalProps > 0 ? (((qtdRecusadas + qtdCanceladas) / totalProps) * 100).toFixed(1) + '%' : '0%'; const elTaxaAp = document.getElementById('dir-kpi-taxa-aprov'); const elSubAp = document.getElementById('dir-kpi-sub-aprov'); const elTaxaRec = document.getElementById('dir-kpi-taxa-recusa'); const elSubRec = document.getElementById('dir-kpi-sub-recusa'); if (elTaxaAp) elTaxaAp.textContent = taxaAprov; if (elSubAp) elSubAp.textContent = `${qtdAprov} aprov. de ${totalProps} (${fmtBRL(valAprov)})`; if (elTaxaRec) elTaxaRec.textContent = taxaRecusa; if (elSubRec) elSubRec.textContent = `${qtdRecusadas} recus. | ${qtdCanceladas} cancel.`; const ctxStatus = document.getElementById('chart-diretoria-status'); if (ctxStatus) { if (chartStatusInst) chartStatusInst.destroy(); chartStatusInst = new Chart(ctxStatus, { type: 'doughnut', data: { labels: ['Aprovados', 'Pendentes de Aprovação', 'Recusados', 'Cancelados'], datasets: [{ data: totalProps === 0 ? [1, 0, 0, 0] : [qtdAprov, qtdPend, qtdRecusadas, qtdCanceladas], backgroundColor: ['#10b981', '#f59e0b', '#e11d48', '#71717a'], borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { font: { family: 'Inter', size: 11 } } } } } }); } const ctxValores = document.getElementById('chart-diretoria-valores'); if (ctxValores) { if (chartValoresInst) chartValoresInst.destroy(); chartValoresInst = new Chart(ctxValores, { type: 'bar', data: { labels: ['Aprovados (R$)', 'Em Análise / Pendentes (R$)', 'Recusados / Cancelados (R$)'], datasets: [{ label: 'Volume Financeiro de Pedidos (R$)', data: [valAprov, valPend, valRecCanc], backgroundColor: ['#059669', '#d97706', '#be123c'], borderRadius: 6 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } } }); } } function gerarPDFPropostaExecutiva(numProposta) { const p = db.propostas.find(x => String(x.numero) === String(numProposta)); if (!p) return; const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)) || {}; const itens = Array.isArray(p.itens) && p.itens.length > 0 ? p.itens : [{ produto: p.produto, pn: 'N/A', qtd: p.qtd || 1, unit: p.total || 0, total: p.total || 0 }]; const subtotal = p.subtotal !== undefined ? Number(p.subtotal) : Number(p.total || 0); const frete = Number(p.frete || 0); const taxasImp = Number(p.taxasImp || 0); const taxaImpPerc = p.taxaImpPerc !== undefined ? Number(p.taxaImpPerc) : (subtotal > 0 && taxasImp > 0 ? Number(((taxasImp / subtotal) * 100).toFixed(2)) : 0); const valorCheioBruto = p.valorCheio !== undefined ? Number(p.valorCheio) : Number(subtotal + frete + taxasImp); const calcPag = calcularValoresPagamentoProposta(valorCheioBruto, p.formaPagamento || 'PIX', p.parcelasCartao || 4); const total = p.total !== undefined ? Number(p.total) : calcPag.totalFinal; const sinal30 = total * 0.30; const saldo70 = total * 0.70; const labelModalidadePDF = calcPag.formaPagamento === 'PIX' ? `PIX COM 5% DE DESCONTO (-${fmtBRL(calcPag.descontoPix5)}) • TOTAL: ${fmtBRL(total)}` : calcPag.formaPagamento === 'CARTAO' ? `CARTÃO DE CRÉDITO EM ${calcPag.parcelasCartao}X DE ${fmtBRL(calcPag.valorParcelaCartao)}${calcPag.parcelasCartao <= 4 ? ' SEM JUROS' : ''} • TOTAL: ${fmtBRL(total)}` : `DINHEIRO À VISTA • TOTAL: ${fmtBRL(total)}`; const detalheUnicoModoPDF = calcPag.formaPagamento === 'PIX' ? `<p style="margin:0 0 6px 0;"><b>Condição no Modo PIX Selecionado:</b><br>• Pagamento via <b>PIX com 5% de desconto</b> aplicado sobre o valor cheio (${fmtBRL(calcPag.valorCheio)}), gerando economia direta de <b>${fmtBRL(calcPag.descontoPix5)}</b> e valor final líquido de <b>${fmtBRL(total)}</b> (ICMS 17%).</p>` : calcPag.formaPagamento === 'CARTAO' ? `<p style="margin:0 0 6px 0;"><b>Condição no Modo Cartão Selecionado:</b><br>• Pagamento via <b>Cartão de Crédito em ${calcPag.parcelasCartao}x de ${fmtBRL(calcPag.valorParcelaCartao)}</b>${calcPag.parcelasCartao <= 4 ? ' sem juros' : ''}, totalizando <b>${fmtBRL(total)}</b> (ICMS 17%).</p>` : `<p style="margin:0 0 6px 0;"><b>Condição no Modo Dinheiro Selecionado:</b><br>• Pagamento à vista em <b>Dinheiro</b> no valor total de <b>${fmtBRL(total)}</b> (ICMS 17%).</p>`; const linhasProdutosHTML = itens.map(it => ` <tr> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; font-weight:600;">${it.produto}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; font-family:monospace;">${it.pn}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; text-align:center;">${String(it.qtd).padStart(2, '0')}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; text-align:right;">${fmtBRL(it.unit)}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; text-align:right; font-weight:700;">${fmtBRL(it.total)}</td> </tr> `).join(''); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Cotação Comercial Nº ${p.numero} — LES AVIATION</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #f4f4f5; margin: 0; padding: 20px; } .page { width: 210mm; min-height: 297mm; background: #ffffff; margin: 0 auto 24px auto; padding: 16mm 18mm; box-shadow: 0 10px 25px rgba(0,0,0,0.08); display: flex; flex-direction: column; justify-content: space-between; page-break-after: always; } .sec-badge { display: inline-flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.12em; color: #09090b; margin-bottom: 10px; } .sec-num { background: #09090b; color: #fff; padding: 2px 7px; border-radius: 3px; font-size: 10px; } .box { border: 1px solid #d4d4d8; border-radius: 6px; padding: 12px 14px; background: #fafafa; } table { width: 100%; border-collapse: collapse; font-size: 12px; } th { background: #09090b; color: #ffffff; text-transform: uppercase; font-size: 10px; letter-spacing: 0.1em; padding: 9px 12px; text-align: left; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; min-height: auto; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES AVIATION</b> • Visualização Oficial de Proposta Comercial (2 Páginas)</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:700; cursor:pointer; font-size:12px;">Salvar como PDF / Imprimir</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:14px; margin-bottom:18px;"> ${obterLogoHTMLDocumento('LES AVIATION • PROPOSTA COMERCIAL EXECUTIVA')} <div style="text-align:right;"> <div style="font-size:18px; font-weight:800; letter-spacing:0.06em; text-transform:uppercase;">COTAÇÃO COMERCIAL PRODUTO AERONÁUTICO</div> <div style="font-size:11px; font-weight:600; color:#3f3f46; margin-top:6px;"> Nº <b style="color:#09090b;">[${p.numero}]</b> | EMISSÃO <b style="color:#09090b;">[${fmtDataBR(p.emissao)}]</b> | VALIDADE <b style="color:#09090b;">[${p.validade}]</b> </div> </div> </div> <div style="margin-bottom:18px;"> <div class="sec-badge"><span class="sec-num">01</span> DADOS DO CLIENTE</div> <div class="box" style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px; font-size:12px;"> <div> <div style="font-size:10px; font-weight:700; color:#71717a; text-transform:uppercase;">NOME DO CLIENTE</div> <div style="font-weight:700; margin-top:3px;">${cli.contato || cli.nome || '-'}</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a; text-transform:uppercase;">EMPRESA / CNPJ</div> <div style="font-weight:700; margin-top:3px;">${cli.nome || '-'} (${cli.cnpj || '-'})</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a; text-transform:uppercase;">CONTATO (E-MAIL | TELEFONE)</div> <div style="font-weight:600; margin-top:3px;">${cli.email || '-'} | ${cli.whatsapp || '-'}</div> </div> <div style="grid-column: span 3; border-top:1px solid #e4e4e7; padding-top:6px;"> <span style="font-size:10px; font-weight:700; color:#71717a; text-transform:uppercase;">ENDEREÇO DE ENTREGA: </span> <span style="font-weight:600;">${cli.endereco || '-'} (${cli.cidade || ''})</span> </div> </div> </div> <div style="margin-bottom:18px;"> <div class="sec-badge"><span class="sec-num">02</span> DESCRIÇÃO DOS PRODUTOS</div> <table style="border:1px solid #d4d4d8;"> <thead> <tr> <th>PRODUTO</th> <th>P/N</th> <th style="text-align:center;">QTD.</th> <th style="text-align:right;">VALOR UNIT.</th> <th style="text-align:right;">TOTAL</th> </tr> </thead> <tbody>${linhasProdutosHTML}</tbody> </table> </div> <div style="display:grid; grid-template-columns:1.15fr 0.85fr; gap:16px; margin-bottom:18px;"> <div> <div class="sec-badge"><span class="sec-num">03</span> MODO DE PAGAMENTO SELECIONADO</div> <div class="box" style="font-size:11px; line-height:1.55;"> <div style="background:#09090b; color:#fff; padding:8px 10px; border-radius:4px; margin-bottom:8px; font-size:10.5px;"> <b>MODO DE PAGAMENTO DEFINIDO NESTA PROPOSTA:</b><br> <span style="color:#34d399; font-weight:800;">${labelModalidadePDF}</span> </div> ${detalheUnicoModoPDF} <div style="border-top:1px solid #e4e4e7; padding-top:8px; margin-top:8px;"> <b>Dados do Emitente:</b><br> Rua Jose pereira liberado 1710, sala 02 galpão 06. Bairro são joão. Itajaí - SC. CEP 88.304-400<br> <b>ALOJA BRASIL COMERCIAL LTDA</b><br> CNPJ 48.657.151/0001-43 • INSC. ESTADUAL 262.018.420 </div> </div> </div> <div> <div class="sec-badge"><span class="sec-num">04</span> RESUMO FINANCEIRO</div> <div class="box" style="font-size:12px;"> <div style="display:flex; justify-content:space-between; padding:5px 0; border-bottom:1px solid #e4e4e7;"> <span style="font-weight:600; color:#52525b;">SUBTOTAL PRODUTOS</span> <b>${fmtBRL(subtotal)}</b> </div> <div style="display:flex; justify-content:space-between; padding:5px 0; border-bottom:1px solid #e4e4e7;"> <span style="font-weight:600; color:#52525b;">FRETE</span> <b>${fmtBRL(frete)}</b> </div> <div style="display:flex; justify-content:space-between; padding:5px 0; border-bottom:1px solid #e4e4e7;"> <span style="font-weight:600; color:#52525b;">TAXA DE IMPORTAÇÃO (${taxaImpPerc}%)</span> <b>${fmtBRL(taxasImp)}</b> </div> ${calcPag.formaPagamento === 'PIX' ? ` <div style="display:flex; justify-content:space-between; padding:5px 0; border-bottom:1px solid #e4e4e7;"> <span style="font-weight:700; color:#18181b;">VALOR BASE S/ DESCONTO</span> <b>${fmtBRL(calcPag.valorCheio)}</b> </div> <div style="display:flex; justify-content:space-between; padding:5px 0; border-bottom:1px solid #e4e4e7; color:#047857;"> <span style="font-weight:800;">DESCONTO PIX (5%)</span> <b>- ${fmtBRL(calcPag.descontoPix5)}</b> </div> ` : calcPag.formaPagamento === 'CARTAO' ? ` <div style="display:flex; justify-content:space-between; padding:5px 0; border-bottom:1px solid #e4e4e7; color:#0369a1;"> <span style="font-weight:800;">CARTÃO (${calcPag.parcelasCartao}X ${calcPag.parcelasCartao <= 4 ? 'S/ JUROS' : ''})</span> <b>${calcPag.parcelasCartao}x de ${fmtBRL(calcPag.valorParcelaCartao)}</b> </div> ` : ''} <div style="display:flex; justify-content:space-between; padding:10px 12px; background:#09090b; color:#fff; border-radius:4px; margin-top:8px; font-size:14px;"> <span style="font-weight:800;">VALOR FINAL (${calcPag.formaPagamento})</span> <span style="font-weight:900; color:#34d399;">${fmtBRL(total)}</span> </div> </div> </div> </div> <div> <div class="sec-badge"><span class="sec-num">05</span> OBSERVAÇÕES</div> <div class="box" style="font-size:11px; line-height:1.5; min-height:58px;"> ${p.exclusoes || 'Sem observações adicionais.'} </div> </div> </div> <div style="border-top:2px solid #09090b; padding-top:10px; display:flex; justify-content:space-between; font-size:11px; font-weight:600;"> <span>Consultor Responsável: <b>${p.vendedor || p.criadoPor}</b> ${p.consultorContato ? `• ${p.consultorContato}` : ''}</span> <span>LES AVIATION • PÁGINA 1/2</span> </div> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:14px; margin-bottom:18px;"> ${obterLogoHTMLDocumento('ALOJA BRASIL COMERCIAL LTDA • CNPJ 48.657.151/0001-43')} <div style="text-align:right;"> <div style="font-size:17px; font-weight:800; text-transform:uppercase;">ACEITE DA PROPOSTA</div> <div style="font-size:11px; font-weight:700; color:#52525b;">CONFIRMAÇÃO COMERCIAL E AUTORIZAÇÃO DO PEDIDO</div> <div style="font-size:11px; margin-top:4px;">COTAÇÃO Nº <b>[${p.numero}]</b> | DATA <b>[${fmtDataBR(p.emissao)}]</b></div> </div> </div> <div style="margin-bottom:18px;"> <div class="sec-badge"><span class="sec-num">01</span> IDENTIFICAÇÃO</div> <div class="box" style="display:grid; grid-template-columns:1.3fr 0.7fr 1fr; gap:12px; font-size:12px;"> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">CLIENTE / RAZÃO SOCIAL</div> <div style="font-weight:700; margin-top:2px;">${cli.nome || '-'}</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">CPF / CNPJ</div> <div style="font-weight:700; margin-top:2px;">${cli.cnpj || '-'}</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">VALOR TOTAL (${calcPag.formaPagamento})</div> <div style="font-weight:900; font-size:14px; margin-top:2px;">${fmtBRL(total)}</div> </div> <div style="border-top:1px solid #e4e4e7; padding-top:8px;"> <div style="font-size:10px; font-weight:700; color:#71717a;">SINAL / PRIMEIRO PAGAMENTO (30%)</div> <div style="font-weight:800; margin-top:2px;">${fmtBRL(sinal30)} (${p.sinalRegra || '30% no pedido'})</div> </div> <div style="border-top:1px solid #e4e4e7; padding-top:8px; grid-column: span 2;"> <div style="font-size:10px; font-weight:700; color:#71717a;">SALDO NA ENTREGA (70%)</div> <div style="font-weight:800; margin-top:2px;">${fmtBRL(saldo70)}</div> </div> </div> </div> <div style="margin-bottom:20px;"> <div class="sec-badge"><span class="sec-num">02</span> TERMOS DE ACEITE</div> <div class="box" style="font-size:11px; line-height:1.55;"> <p style="margin:0 0 8px 0;"><b>1. OBJETO E ESPECIFICAÇÕES:</b> O aceite abrange exclusivamente os produtos, P/N, quantidades, características técnicas, valores e demais condições descritas na proposta e em seus anexos.</p> <p style="margin:0 0 8px 0;"><b>2. PREÇOS E DESCONTOS:</b> Os preços, descontos, frete, tributos e taxas de importação válidos são apenas os expressamente indicados. Qualquer alteração deverá ser formalizada em nova cotação ou aditivo.</p> <p style="margin:0 0 8px 0;"><b>3. ACEITE E INÍCIO DO PEDIDO:</b> A proposta será considerada aceita após a assinatura deste termo e a confirmação do sinal ou primeiro pagamento. O pedido ao fornecedor no exterior somente será realizado após a efetiva compensação desse valor.</p> <p style="margin:0 0 8px 0;"><b>4. PAGAMENTO DO SALDO:</b> O saldo remanescente deverá ser pago na disponibilização ou entrega do produto, antes de sua liberação ao cliente, salvo condição diferente expressamente indicada na proposta.</p> <p style="margin:0 0 8px 0;"><b>5. CANCELAMENTO PELO CLIENTE:</b> O pagamento inicial constitui sinal e princípio de pagamento e será abatido do preço total. Após a formalização do pedido internacional, eventual cancelamento imotivado pelo cliente poderá resultar na retenção do sinal, limitada aos custos e compromissos assumidos, ressalvados os direitos obrigatórios previstos em lei.</p> <p style="margin:0;"><b>6. PRAZO E EVENTOS EXTERNOS:</b> Os prazos de fornecimento são estimados e podem ser afetados por disponibilidade do fabricante, transporte internacional, liberação aduaneira ou eventos fora do controle razoável da LES Aviation, com comunicação ao cliente.</p> </div> </div> <div> <div class="sec-badge"><span class="sec-num">03</span> DECLARAÇÃO E ASSINATURAS</div> <p style="font-size:11px; color:#3f3f46; margin-bottom:18px;"> Ao assinar, as partes confirmam a leitura e concordância com a proposta e com os termos acima, autorizando o prosseguimento do atendimento. </p> <div style="font-size:12px; font-weight:700; margin-bottom:36px;"> LOCAL E DATA: ${cli.cidade || 'Itajaí - SC / Anápolis - GO'}, ____ DE ________________ DE ${new Date().getFullYear()} </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; margin-top:24px;"> <div style="border-top:1.5px solid #09090b; padding-top:8px; font-size:11px;"> <b>CLIENTE / RESPONSÁVEL</b><br> ${p.signatarioCliente || cli.contato || cli.nome || '[NOME DO SIGNATÁRIO]'}<br> <span style="color:#71717a;">CPF/CNPJ: ${cli.cnpj || ''}</span> </div> <div style="border-top:1.5px solid #09090b; padding-top:8px; font-size:11px;"> <b>LES AVIATION / ALOJA BRASIL COMERCIAL LTDA</b><br> Emitido por: ${p.vendedor || p.criadoPor} ${p.aprovadoPor ? `• Aprovado por: ${p.aprovadoPor}` : ''}<br> <span style="color:#71717a;">CNPJ 48.657.151/0001-43</span> </div> </div> </div> </div> <div style="border-top:2px solid #09090b; padding-top:10px; display:flex; justify-content:space-between; font-size:11px; font-weight:600;"> <span>Rastreabilidade LES ERP: Gerado por ${p.criadoPor} (${p.criadoEm})</span> <span>LES AVIATION • PÁGINA 2/2</span> </div> </div> </body> </html> `); win.document.close(); } function gerarPDFPurchaseOrder(numPO) { const po = db.pos.find(x => x.numero === numPO); if (!po) return; const cli = db.clientes.find(c => Number(c.id) === Number(po.clienteId)) || {}; const endEntrega = po.enderecoEntrega || cli.endereco || 'Endereço conforme cadastro do cliente'; const itens = Array.isArray(po.itens) && po.itens.length > 0 ? po.itens : [{ produto: po.produto, pn: 'CONFORME COTAÇÃO', qtd: po.qtd || 1, unit: po.valorNum || 0, total: po.valorNum || 0 }]; const anexos = po.anexosPO || {}; const trk = po.tracking || {}; const statusAnexo = (slot) => slot ? `✓ VALIDADO POR VENDAS (${slot.nome}${slot.info ? ' • ' + slot.info : ''} — por ${slot.enviadoPor})` : 'PENDENTE DE INSERÇÃO PELO COMERCIAL/VENDAS'; const linhasHTML = itens.map(it => ` <tr> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; font-weight:600;">${it.produto}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; font-family:monospace;">${it.pn}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; text-align:center;">${String(it.qtd).padStart(2, '0')}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; text-align:right;">${fmtBRL(it.unit)}</td> <td style="padding:10px 12px; border-bottom:1px solid #e4e4e7; text-align:right; font-weight:700;">${fmtBRL(it.total)}</td> </tr> `).join(''); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Purchase Order ${po.numero} — LES AVIATION</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #f4f4f5; margin: 0; padding: 20px; } .page { width: 210mm; min-height: 297mm; background: #ffffff; margin: 0 auto; padding: 16mm 18mm; box-shadow: 0 10px 25px rgba(0,0,0,0.08); display: flex; flex-direction: column; justify-content: space-between; } .sec-badge { display: inline-flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 10px; } .sec-num { background: #09090b; color: #fff; padding: 2px 7px; border-radius: 3px; font-size: 10px; } .box { border: 1px solid #d4d4d8; border-radius: 6px; padding: 12px 14px; background: #fafafa; } table { width: 100%; border-collapse: collapse; font-size: 12px; } th { background: #09090b; color: #ffffff; text-transform: uppercase; font-size: 10px; padding: 9px 12px; text-align: left; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES AVIATION</b> • Documento Executivo de Purchase Order (P.O.) & Tracking</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:700; cursor:pointer; font-size:12px;">Salvar PDF do P.O. / Imprimir</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:14px; margin-bottom:18px;"> ${obterLogoHTMLDocumento('ALOJA BRASIL COMERCIAL LTDA • CNPJ 48.657.151/0001-43')} <div style="text-align:right;"> <div style="font-size:18px; font-weight:800; letter-spacing:0.06em; text-transform:uppercase;">PURCHASE ORDER (P.O.) — PEDIDO DE COMPRA</div> <div style="font-size:11px; font-weight:600; color:#3f3f46; margin-top:6px;"> P.O. Nº <b>[${po.numero}]</b> | REF. COTAÇÃO <b>[${po.refVenda}]</b> | EMISSÃO <b>[${fmtDataBR(po.emissao)}]</b> </div> </div> </div> <div style="margin-bottom:16px;"> <div class="sec-badge"><span class="sec-num">01</span> FORNECEDOR, CLIENTE VINCULADO E ENDEREÇO DE ENTREGA</div> <div class="box" style="display:grid; grid-template-columns:1fr 1fr; gap:12px; font-size:12px;"> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">FORNECEDOR / FABRICANTE</div> <div style="font-weight:800; margin-top:2px;">${po.fornecedor}</div> <div style="font-size:11px; color:#52525b; margin-top:2px;">Incoterm / Rota: ${po.incoterm || 'EXW'}</div> </div> <div> <div style="font-size:10px; font-weight:700; color:#71717a;">CLIENTE FINAL VINCULADO</div> <div style="font-weight:800; margin-top:2px;">${cli.nome || '-'} (${cli.cnpj || '-'})</div> <div style="font-size:11px; color:#52525b; margin-top:2px;">Contato: ${cli.contato || '-'} | ${cli.whatsapp || ''}</div> </div> <div style="grid-column: span 2; border-top:1px solid #e4e4e7; padding-top:8px;"> <div style="font-size:10px; font-weight:700; color:#71717a;">ENDEREÇO DE ENTREGA DO CLIENTE (SHIP TO / DESTINO FINAL)</div> <div style="font-weight:800; color:#09090b; margin-top:2px;">${endEntrega}</div> </div> </div> </div> <div style="margin-bottom:16px;"> <div class="sec-badge"><span class="sec-num">02</span> ESPECIFICAÇÃO TÉCNICA DOS PRODUTOS SOLICITADOS</div> <table style="border:1px solid #d4d4d8;"> <thead> <tr><th>PRODUTO / EQUIPAMENTO</th><th>P/N</th><th style="text-align:center;">QTD.</th><th style="text-align:right;">VALOR UNIT.</th><th style="text-align:right;">TOTAL</th></tr> </thead> <tbody>${linhasHTML}</tbody> </table> </div> <div style="margin-bottom:16px;"> <div class="sec-badge"><span class="sec-num">03</span> DOSSIÊ COMERCIAL (VENDAS) & RASTREAMENTO LOGÍSTICO ATUAL</div> <div class="box" style="font-size:11px; line-height:1.7;"> <div>• <b>1. Proposta Comercial Aceita:</b> ${statusAnexo(anexos.proposta)}</div> <div>• <b>2. Cotação Oficial do Fabricante (Perfil Vendas):</b> ${statusAnexo(anexos.cotacaoDRS)}</div> <div>• <b>3. Comprovante de Pagamento do Cliente (Perfil Vendas):</b> ${statusAnexo(anexos.comprovantePagto)}</div> <div style="border-top:1px solid #e4e4e7; margin-top:6px; padding-top:6px;"> • <b>Posição Atual de Rastreamento (Logística):</b> ${po.status} — <b>Onde está:</b> ${trk.localAtual || '-'} (${trk.processoAtual || '-'}) ${trk.awb ? `• AWB/BL: ${trk.awb}` : ''} </div> </div> </div> <div style="display:grid; grid-template-columns:1.15fr 0.85fr; gap:16px;"> <div> <div class="sec-badge"><span class="sec-num">04</span> DADOS DO ADQUIRENTE / EMITENTE</div> <div class="box" style="font-size:11px; line-height:1.55;"> <b>ALOJA BRASIL COMERCIAL LTDA (LES AVIATION)</b><br> CNPJ: 48.657.151/0001-43 • INSC. ESTADUAL: 262.018.420<br> Rua Jose pereira liberado 1710, sala 02 galpão 06. Bairro são joão. Itajaí - SC. CEP 88.304-400 </div> </div> <div> <div class="sec-badge"><span class="sec-num">05</span> CONSOLIDADO DO PEDIDO</div> <div class="box" style="font-size:12px;"> <div style="display:flex; justify-content:space-between; padding:6px 0; border-bottom:1px solid #e4e4e7;"> <span>FRETE + TAXAS IMP.</span> <b>${fmtBRL((po.frete || 0) + (po.taxasImp || 0))}</b> </div> <div style="display:flex; justify-content:space-between; padding:10px 12px; background:#09090b; color:#fff; border-radius:4px; margin-top:10px; font-size:14px;"> <span style="font-weight:800;">VALOR TOTAL P.O.</span> <span style="font-weight:900;">${po.valor || fmtBRL(po.valorNum)}</span> </div> </div> </div> </div> </div> <div style="border-top:2px solid #09090b; padding-top:12px; display:flex; justify-content:space-between; font-size:11px;"> <span>Pedido originado por: <b>${po.criadoPor || '-'}</b> • Aprovado Diretoria: <b>${po.aprovadoPor || '-'}</b></span> <span>LES AVIATION • PURCHASE ORDER OFICIAL</span> </div> </div> </body> </html> `); win.document.close(); } function preencherEnderecoClienteNoPO() { const cliId = Number(document.getElementById('po-cliente')?.value); const cli = db.clientes.find(c => Number(c.id) === cliId); const inputEnd = document.getElementById('po-endereco-entrega'); if (cli && inputEnd && !document.getElementById('po-edit-original-num').value) { inputEnd.value = `${cli.endereco || ''}${cli.cidade && !(cli.endereco || '').includes(cli.cidade) ? ' — ' + cli.cidade : ''}`; } } function salvarCliente(e) { e.preventDefault(); const editId = document.getElementById('cli-edit-id')?.value.trim(); const cnpj = document.getElementById('cli-cnpj').value.trim(); const agora = carimboHoraAtual(); if (editId) { const idx = db.clientes.findIndex(c => String(c.id) === String(editId)); if (idx >= 0) { db.clientes[idx] = { ...db.clientes[idx], nome: document.getElementById('cli-nome').value.trim(), fantasia: document.getElementById('cli-fantasia').value.trim(), cnpj, contato: document.getElementById('cli-contato').value.trim(), whatsapp: document.getElementById('cli-whatsapp').value.trim(), email: document.getElementById('cli-email').value.trim(), cidade: document.getElementById('cli-cidade').value.trim(), origem: document.getElementById('cli-origem').value.trim(), drone: document.getElementById('cli-drone').value.trim(), peso: parseFloat(document.getElementById('cli-peso').value) || 0, qtd: parseInt(document.getElementById('cli-qtd').value) || 1, pagamento: document.getElementById('cli-pagamento').value.trim(), endereco: document.getElementById('cli-endereco').value.trim(), obs: document.getElementById('cli-obs').value.trim(), modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('VENDAS', `Editou cadastro do cliente "${db.clientes[idx].nome}"`, cnpj); cancelarEdicaoCliente(); saveDB(); return; } } if (db.clientes.some(c => c.cnpj === cnpj)) { alert('Cliente já cadastrado com este CNPJ/CPF. Use o botão "Editar" na tabela abaixo para atualizar seus dados.'); return; } const novo = { id: Date.now(), nome: document.getElementById('cli-nome').value.trim(), fantasia: document.getElementById('cli-fantasia').value.trim(), cnpj, contato: document.getElementById('cli-contato').value.trim(), whatsapp: document.getElementById('cli-whatsapp').value.trim(), email: document.getElementById('cli-email').value.trim(), cidade: document.getElementById('cli-cidade').value.trim(), origem: document.getElementById('cli-origem').value.trim(), drone: document.getElementById('cli-drone').value.trim(), peso: parseFloat(document.getElementById('cli-peso').value) || 0, qtd: parseInt(document.getElementById('cli-qtd').value) || 1, pagamento: document.getElementById('cli-pagamento').value.trim(), endereco: document.getElementById('cli-endereco').value.trim(), obs: document.getElementById('cli-obs').value.trim(), criadoPor: sessaoAtual.nome, criadoEm: agora }; db.clientes.push(novo); registrarAuditoria('VENDAS', `Cadastrou cliente "${novo.nome}"`, novo.cnpj); cancelarEdicaoCliente(); saveDB(); } function editarClienteExistente(id) { const c = db.clientes.find(x => String(x.id) === String(id)); if (!c) return; switchSubTab('ven', 'clientes'); document.getElementById('cli-edit-id').value = c.id; document.getElementById('cli-contato').value = c.contato || ''; document.getElementById('cli-nome').value = c.nome || ''; document.getElementById('cli-fantasia').value = c.fantasia || ''; document.getElementById('cli-cnpj').value = c.cnpj || ''; document.getElementById('cli-whatsapp').value = c.whatsapp || ''; document.getElementById('cli-email').value = c.email || ''; document.getElementById('cli-cidade').value = c.cidade || ''; document.getElementById('cli-origem').value = c.origem || ''; document.getElementById('cli-drone').value = c.drone || ''; document.getElementById('cli-peso').value = c.peso || 0; document.getElementById('cli-qtd').value = c.qtd || 1; document.getElementById('cli-pagamento').value = c.pagamento || ''; document.getElementById('cli-endereco').value = c.endereco || ''; document.getElementById('cli-obs').value = c.obs || ''; document.getElementById('cli-form-titulo').textContent = `Editando Cliente: ${c.nome}`; document.getElementById('btn-submit-cli').textContent = `Salvar Alterações (${c.nome})`; document.getElementById('btn-cancelar-edicao-cli').classList.remove('hidden'); document.getElementById('card-form-cliente')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoCliente() { const f = document.getElementById('form-cliente'); if (f) f.reset(); const elId = document.getElementById('cli-edit-id'); if (elId) elId.value = ''; const tit = document.getElementById('cli-form-titulo'); if (tit) tit.textContent = 'Cadastro / Edição de Cliente'; const btn = document.getElementById('btn-submit-cli'); if (btn) btn.textContent = 'Salvar Cliente'; document.getElementById('btn-cancelar-edicao-cli')?.classList.add('hidden'); } function salvarOportunidade(e) { e.preventDefault(); const op = { id: 'OP-' + Date.now().toString().slice(-5), clienteId: Number(document.getElementById('op-cliente').value), titulo: document.getElementById('op-titulo').value.trim(), categoria: document.getElementById('op-categoria').value, etapa: document.getElementById('op-etapa').value, valor: parseFloat(document.getElementById('op-valor').value) || 0, retorno: document.getElementById('op-retorno').value, origem: document.getElementById('op-origem').value.trim(), criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }; db.oportunidades.unshift(op); registrarAuditoria('VENDAS', `Registrou oportunidade "${op.titulo}"`, op.id); e.target.reset(); saveDB(); } function salvarAnamnese(e) { e.preventDefault(); const demanda = document.getElementById('anam-demanda').value; const mtow = document.getElementById('anam-peso-mtow').value; const pesoOp = document.getElementById('anam-peso-op').value; const fixacao = document.getElementById('anam-pq-fixacao').value.trim(); let statusQualificacao = 'Qualificação Preliminar Completa'; if (demanda === 'PARAQUEDAS' && (!mtow || !pesoOp || !fixacao)) { statusQualificacao = 'Qualificação técnica incompleta'; } const ficha = { id: 'ANAM-' + Date.now().toString().slice(-5), clienteId: Number(document.getElementById('anam-cliente').value), demanda, palavrasCliente: document.getElementById('anam-palavras-cliente').value.trim(), droneModelo: document.getElementById('anam-drone-mod').value.trim(), pesoEquipamento: document.getElementById('anam-peso-eq').value || '-', pesoPayload: document.getElementById('anam-peso-payload').value || '-', pesoMtow: mtow || 'Pendente', pesoOperacional: pesoOp || 'Pendente', orcamento: document.getElementById('anam-orcamento').value.trim(), proxAcao: document.getElementById('anam-prox-acao').value.trim(), statusQualificacao, criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }; db.anamneses.unshift(ficha); registrarAuditoria('VENDAS', `Registrou Anamnese (${statusQualificacao})`, ficha.id); e.target.reset(); saveDB(); } function salvarPO(e) { e.preventDefault(); const editOriginal = document.getElementById('po-edit-original-num').value.trim(); const numero = document.getElementById('po-numero').value.trim(); const refVenda = document.getElementById('po-ref-venda').value.trim(); const valorNum = parseFloat(document.getElementById('po-valor-num').value) || 0; const emissao = document.getElementById('po-emissao').value || new Date().toISOString().slice(0, 10); const cliId = Number(document.getElementById('po-cliente').value); const cli = db.clientes.find(c => Number(c.id) === cliId); const endEntrega = document.getElementById('po-endereco-entrega').value.trim() || (cli ? cli.endereco : ''); const etapaCod = document.getElementById('po-etapa-inicial').value; const defEtapa = obterDefEtapaTracking(etapaCod); const agora = carimboHoraAtual(); if (editOriginal) { const idx = db.pos.findIndex(p => String(p.numero) === String(editOriginal)); if (idx >= 0) { const ant = db.pos[idx]; ant.tracking = ant.tracking || { historico: [] }; ant.tracking.etapaCodigo = etapaCod; db.pos[idx] = { ...ant, numero, refVenda, clienteId: cliId, enderecoEntrega: endEntrega, fornecedor: document.getElementById('po-fornecedor').value.trim(), produto: document.getElementById('po-produto').value.trim(), qtd: Number(document.getElementById('po-qtd').value) || 1, valorNum, valor: fmtBRL(valorNum), incoterm: document.getElementById('po-incoterm').value.trim(), emissao, previsao: document.getElementById('po-previsao').value, status: defEtapa.label, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('LOGISTICA', `Editou P.O. ${numero}`, numero); cancelarEdicaoPO(); saveDB(); return; } } const novoPO = { numero, refVenda, clienteId: cliId, enderecoEntrega: endEntrega, fornecedor: document.getElementById('po-fornecedor').value.trim(), produto: document.getElementById('po-produto').value.trim(), qtd: Number(document.getElementById('po-qtd').value) || 1, valorNum, valor: fmtBRL(valorNum), incoterm: document.getElementById('po-incoterm').value.trim(), emissao, previsao: document.getElementById('po-previsao').value, status: defEtapa.label, criadoPor: sessaoAtual.nome, criadoEm: agora, anexosPO: { proposta: null, cotacaoDRS: null, comprovantePagto: null }, tracking: { etapaCodigo: etapaCod, localAtual: 'Graz, Áustria (Fabricante)', processoAtual: defEtapa.label, awb: '', diDuimp: '', eta: document.getElementById('po-previsao').value, obs: 'P.O. emitido manualmente na Logística', atualizadoPor: sessaoAtual.nome, atualizadoEm: agora, historico: [ { dataHora: agora, etapa: defEtapa.label, local: 'Graz, Áustria', processo: 'Abertura do P.O.', obs: '', usuario: sessaoAtual.nome } ] } }; db.pos.unshift(novoPO); if (valorNum > 0 && !db.financeiro.lancamentos.some(l => l.doc === numero)) { db.financeiro.lancamentos.unshift({ id: `FIN-${numero}`, natureza: 'COMPRA_ESTOQUE', categoria: 'Importação / Compras Internacionais', desc: `Pedido de Compra ${numero} — ${novoPO.produto}`, cc: 'Logística & Importação', entidade: novoPO.fornecedor, doc: numero, competencia: emissao, vencimento: novoPO.previsao || emissao, bruto: valorNum, impostos: 0, ajuste: 0, liquido: valorNum, status: 'PREVISTO', conta: 'Banco Principal LES', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome} (Auto PO->Fin)`, criadoEm: agora }); } registrarAuditoria('LOGISTICA', `Registrou P.O. ${numero} e iniciou Rastreamento`, numero); cancelarEdicaoPO(); saveDB(); } function editarPOExistente(numPO) { const p = db.pos.find(x => String(x.numero) === String(numPO)); if (!p) return; const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); document.getElementById('po-edit-original-num').value = p.numero; document.getElementById('po-numero').value = p.numero; document.getElementById('po-ref-venda').value = p.refVenda || ''; document.getElementById('po-cliente').value = p.clienteId; document.getElementById('po-endereco-entrega').value = p.enderecoEntrega || (cli ? cli.endereco : ''); document.getElementById('po-fornecedor').value = p.fornecedor || ''; document.getElementById('po-produto').value = p.produto || ''; document.getElementById('po-qtd').value = p.qtd || 1; document.getElementById('po-valor-num').value = p.valorNum || 0; document.getElementById('po-incoterm').value = p.incoterm || ''; document.getElementById('po-emissao').value = p.emissao || ''; document.getElementById('po-previsao').value = p.previsao || ''; document.getElementById('po-etapa-inicial').value = p.tracking?.etapaCodigo || '2_PO_EMITIDO'; document.getElementById('po-form-titulo').textContent = `Editando Purchase Order ${p.numero}`; document.getElementById('btn-submit-po').textContent = `Salvar Alterações (${p.numero})`; document.getElementById('btn-cancelar-edicao-po').classList.remove('hidden'); switchSubTab('log', 'pos'); document.getElementById('form-po').scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoPO() { const form = document.getElementById('form-po'); if (form) form.reset(); document.getElementById('po-edit-original-num').value = ''; document.getElementById('po-form-titulo').textContent = 'Emitir / Editar Purchase Order (P.O.)'; document.getElementById('btn-submit-po').textContent = 'Salvar P.O. & Iniciar Rastreamento'; document.getElementById('btn-cancelar-edicao-po').classList.add('hidden'); preencherEnderecoClienteNoPO(); } function salvarFornecedor(e) { e.preventDefault(); const nome = document.getElementById('forn-nome').value.trim(); db.fornecedores.push({ id: Date.now(), nome, pais: document.getElementById('forn-pais').value.trim(), contato: document.getElementById('forn-contato').value.trim(), moeda: document.getElementById('forn-moeda').value.trim(), prazos: document.getElementById('forn-prazos').value.trim(), produtos: document.getElementById('forn-produtos').value.trim(), criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); registrarAuditoria('LOGISTICA', `Cadastrou fornecedor ${nome}`, nome); e.target.reset(); saveDB(); } function calcularDatasAutomaticasSN() { const pnSel = document.getElementById('sn-pn')?.value; const dataInst = document.getElementById('sn-instalacao')?.value; if (!dataInst) return; const pnObj = db.pns.find(p => p.pn === pnSel); document.getElementById('sn-shelflife').value = adicionarMesesData(dataInst, pnObj ? pnObj.shelf : 24); document.getElementById('sn-revisao').value = adicionarMesesData(dataInst, 12); } function salvarPN(e) { e.preventDefault(); const pn = document.getElementById('pn-codigo').value.trim(); if (db.pns.some(x => x.pn === pn)) { alert('Este P/N já existe na base.'); return; } db.pns.push({ pn, desc: document.getElementById('pn-desc').value.trim(), shelf: parseInt(document.getElementById('pn-shelf').value) || 24, revMeses: 12, criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); registrarAuditoria('POS_VENDA', `Cadastrou PN ${pn} no catálogo`, pn); e.target.reset(); saveDB(); } function salvarSN(e) { e.preventDefault(); const editOriginal = document.getElementById('sn-edit-original')?.value.trim(); const snNum = document.getElementById('sn-numero').value.trim(); const agora = carimboHoraAtual(); if (editOriginal) { const idx = db.sns.findIndex(s => String(s.sn) === String(editOriginal)); if (idx >= 0) { db.sns[idx] = { ...db.sns[idx], sn: snNum, pn: document.getElementById('sn-pn').value, clienteId: Number(document.getElementById('sn-cliente').value), snAnv: document.getElementById('sn-anv').value.trim(), instalacao: document.getElementById('sn-instalacao').value, shelfLife: document.getElementById('sn-shelflife').value, horas: parseFloat(document.getElementById('sn-horas').value) || 0, revisao: document.getElementById('sn-revisao').value, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('POS_VENDA', `Editou S.N. ${snNum}`, snNum); cancelarEdicaoSN(); saveDB(); return; } } if (db.sns.some(s => s.sn === snNum)) { alert('S.N. já cadastrado. Use o botão "Editar" na tabela abaixo para atualizar seus dados.'); return; } db.sns.push({ sn: snNum, pn: document.getElementById('sn-pn').value, clienteId: Number(document.getElementById('sn-cliente').value), snAnv: document.getElementById('sn-anv').value.trim(), instalacao: document.getElementById('sn-instalacao').value, shelfLife: document.getElementById('sn-shelflife').value, horas: parseFloat(document.getElementById('sn-horas').value) || 0, revisao: document.getElementById('sn-revisao').value, acionamentos: 0, anexos: [], criadoPor: sessaoAtual.nome, criadoEm: agora }); registrarAuditoria('POS_VENDA', `Cadastrou S.N. ${snNum}`, snNum); cancelarEdicaoSN(); saveDB(); } function editarSNExistente(snNumero) { const s = db.sns.find(x => String(x.sn) === String(snNumero)); if (!s) return; switchSubTab('pos', 'frota'); document.getElementById('sn-edit-original').value = s.sn; document.getElementById('sn-numero').value = s.sn; document.getElementById('sn-pn').value = s.pn; document.getElementById('sn-cliente').value = s.clienteId; document.getElementById('sn-anv').value = s.snAnv || ''; document.getElementById('sn-instalacao').value = s.instalacao || ''; document.getElementById('sn-shelflife').value = s.shelfLife || ''; document.getElementById('sn-horas').value = s.horas || 0; document.getElementById('sn-revisao').value = s.revisao || ''; document.getElementById('sn-form-titulo').textContent = `Editando Equipamento S.N. ${s.sn}`; document.getElementById('btn-submit-sn').textContent = `Salvar Alterações (${s.sn})`; document.getElementById('btn-cancelar-edicao-sn').classList.remove('hidden'); document.getElementById('card-form-sn')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoSN() { const f = document.getElementById('form-sn'); if (f) f.reset(); const elOrig = document.getElementById('sn-edit-original'); if (elOrig) elOrig.value = ''; const tit = document.getElementById('sn-form-titulo'); if (tit) tit.textContent = 'Cadastro / Edição Técnica de S.N. (Revisão Programada: +12m da 1ª Instalação)'; const btn = document.getElementById('btn-submit-sn'); if (btn) btn.textContent = 'Registrar S.N.'; document.getElementById('btn-cancelar-edicao-sn')?.classList.add('hidden'); } function removerSN(sn) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Excluir S.N. (Diretoria Drones / Holding)')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`sn:${sn}`); db.sns = db.sns.filter(s => s.sn !== sn); registrarAuditoria('POS_VENDA', `Excluiu S.N. ${sn}`, sn); saveDB(); } function abrirFichaSN(snNumero) { snSelecionadoAtual = snNumero; switchSubTab('pos', 'sndetalhe'); renderFichaSN(); } function abrirAnexoEquipamentoBlob(snNumero, idxAnexo) { const snObj = db.sns.find(s => s.sn === snNumero); if (!snObj || !snObj.anexos || !snObj.anexos[idxAnexo]) return; const a = snObj.anexos[idxAnexo]; const urlBlob = converterDataUrlParaBlobUrl(a.url); if (urlBlob) window.open(urlBlob, '_blank'); else alert('O arquivo deste anexo não está disponível.'); } function renderFichaSN() { const snObj = db.sns.find(s => s.sn === snSelecionadoAtual); if (!snObj) return; const cli = db.clientes.find(c => Number(c.id) === Number(snObj.clienteId)); const logHistorico = db.logbook.filter(l => l.sn === snObj.sn); const anexos = snObj.anexos || []; document.getElementById('ativo-detalhe-conteudo').innerHTML = ` <div class="les-card p-6 space-y-6"> <div class="border-b pb-4 flex justify-between items-center"> <div><h2 class="text-2xl font-extrabold font-mono">${snObj.sn}</h2>${etiquetaRastreabilidade(snObj)}</div> <div class="text-right"><span class="text-xs text-zinc-500 block">SN ANV</span><b class="font-mono text-sky-700">${snObj.snAnv}</b></div> </div> <div class="grid grid-cols-1 md:grid-cols-4 gap-4 text-xs"> <div class="bg-zinc-50 p-3 rounded-lg border"><b>Cliente:</b> ${cli ? cli.nome : '-'}</div> <div class="bg-zinc-50 p-3 rounded-lg border"><b>PN:</b> ${snObj.pn}</div> <div class="bg-zinc-50 p-3 rounded-lg border"><b>Horas:</b> ${snObj.horas}h (${snObj.acionamentos || 0} acion.)</div> <div class="bg-zinc-50 p-3 rounded-lg border"><b>Shelf:</b> ${snObj.shelfLife} | <b>Rev:</b> ${snObj.revisao}</div> </div> <div class="border-t pt-4 space-y-3"> <h3 class="font-bold text-xs uppercase">Anexos PDF do Equipamento</h3> <div class="flex gap-2 items-end text-xs"> <input type="text" id="anexo-titulo" placeholder="Título do PDF" class="border rounded-lg px-2.5 py-1.5"> <input type="file" id="anexo-file" accept=".pdf,image/*" class="text-xs"> <button onclick="adicionarAnexoAtivo()" class="bg-zinc-950 text-white px-4 py-1.5 rounded-lg font-semibold">Anexar PDF</button> </div> <div class="grid grid-cols-1 md:grid-cols-3 gap-2"> ${anexos.map((a, idx) => `<div class="p-2 border rounded-lg flex justify-between items-center text-xs"><span><b>${a.titulo}</b> (${a.criadoPor || '-'})</span><button type="button" onclick="abrirAnexoEquipamentoBlob('${snObj.sn}', ${idx})" class="text-sky-700 underline font-bold">Abrir PDF</button></div>`).join('')} </div> </div> <div class="border-t pt-4"> <h3 class="font-bold text-xs uppercase mb-2">Histórico Logbook deste S.N.</h3> <table class="w-full text-left text-xs"><tbody class="divide-y">${logHistorico.map(l => `<tr><td class="p-2">${l.data}</td><td class="p-2 font-bold">${l.tipo}</td><td class="p-2">${l.horas}h</td><td class="p-2">${l.desc}</td><td class="p-2">${l.criadoPor || l.tecnico}</td></tr>`).join('')}</tbody></table> </div> </div> `; } function adicionarAnexoAtivo() { const titulo = document.getElementById('anexo-titulo').value.trim(); const fileInput = document.getElementById('anexo-file'); if (!titulo || !fileInput.files.length) return; const reader = new FileReader(); reader.onload = evt => { const snObj = db.sns.find(s => s.sn === snSelecionadoAtual); if (snObj) { snObj.anexos = snObj.anexos || []; snObj.anexos.push({ titulo, url: evt.target.result, data: carimboHoraAtual(), criadoPor: sessaoAtual.nome }); registrarAuditoria('POS_VENDA', `Anexou PDF "${titulo}" ao S.N.`, snObj.sn); saveDB(); renderFichaSN(); } }; reader.readAsDataURL(fileInput.files[0]); } function salvarIntegracao(e) { e.preventDefault(); const sn = document.getElementById('int-sn').value; db.integracoes.unshift({ id: Date.now(), sn, responsavel: document.getElementById('int-resp').value.trim(), data: document.getElementById('int-data').value, status: document.getElementById('int-status').value, testes: document.getElementById('int-testes').value.trim(), criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); registrarAuditoria('POS_VENDA', `Registrou integração no S.N. ${sn}`, sn); e.target.reset(); saveDB(); } function salvarLogbook(e) { e.preventDefault(); const sn = document.getElementById('log-sn').value; const tipo = document.getElementById('log-tipo').value; const horas = parseFloat(document.getElementById('log-horas').value) || 0; db.logbook.unshift({ id: Date.now(), sn, tipo, origemHoras: document.getElementById('log-origem-horas').value, data: document.getElementById('log-data').value, horas, desc: document.getElementById('log-desc').value.trim(), tecnico: sessaoAtual.nome, criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); const item = db.sns.find(s => s.sn === sn); if (item) { if (horas > item.horas) item.horas = horas; if (tipo === 'Acionamento') item.acionamentos = (Number(item.acionamentos) || 0) + 1; } registrarAuditoria('POS_VENDA', `Lançou Logbook (${tipo})`, sn); e.target.reset(); saveDB(); } function removerLogbook(id) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Excluir Logbook (Diretoria Drones / Holding)')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${id}`); db.logbook = db.logbook.filter(l => l.id !== id); registrarAuditoria('POS_VENDA', `Excluiu item de Logbook`, String(id)); saveDB(); } // ========================================================= // PÓS-VENDA E MRO (VISUAL LIMPO, EDIÇÃO DE O.S. E DOCUMENTO CONSOLIDADO) // ========================================================= function diagnosticarNecessidadesPosVendaPO(po, cli) { const prodTxt = normalizarTexto(po.produto || ''); const modeloDrone = cli?.drone || 'Drone do Cliente'; if (prodTxt.includes('drs') || prodTxt.includes('paraquedas') || prodTxt.includes('recuperacao')) { return [ `Kit de Integração e suporte p/ ${modeloDrone}`, `Agendamento de instalação e treinamento operacional`, `Cadastro na Frota S.N. e revisão preventiva (+12 meses)` ]; } return [ `Conferência técnica de recebimento (${modeloDrone})`, `Configuração inicial, calibração e abertura de prontuário`, `Plano de revisão periódica preventiva` ]; } function salvarStatusPreparacaoPOPosVenda(numPO) { const po = db.pos.find(p => String(p.numero) === String(numPO)); if (!po) return; const selStatus = document.getElementById(`prep-status-${numPO}`); const inpObs = document.getElementById(`prep-obs-${numPO}`); po.posVendaPrep = { statusPrep: selStatus ? selStatus.value : 'Aguardando chegada p/ integração', responsavelPrep: sessaoAtual.nome, observacaoPrep: inpObs ? inpObs.value.trim() : '', atualizadoEm: carimboHoraAtual() }; registrarAuditoria('POS_VENDA', `Atualizou status Pós-Venda do ${numPO}: ${po.posVendaPrep.statusPrep}`, numPO); saveDB(); } function prepararCadastroSNDePO(numPO) { const po = db.pos.find(p => String(p.numero) === String(numPO)); if (!po) return; switchSubTab('pos', 'frota'); const selCli = document.getElementById('sn-cliente'); if (selCli) selCli.value = po.clienteId; const inpInst = document.getElementById('sn-instalacao'); if (inpInst) { inpInst.value = new Date().toISOString().slice(0, 10); calcularDatasAutomaticasSN(); } document.getElementById('sn-numero')?.focus(); } function abrirOSManutencaoDePO(numPO) { const po = db.pos.find(p => String(p.numero) === String(numPO)); if (!po) return; const cli = db.clientes.find(c => Number(c.id) === Number(po.clienteId)); switchSubTab('pos', 'mro_drones'); cancelarEdicaoOSMRO(); document.getElementById('mro-numero-os').value = `OS-${po.refVenda || Date.now().toString().slice(-4)}`; document.getElementById('mro-abertura').value = new Date().toISOString().slice(0, 10); document.getElementById('mro-cliente').value = po.clienteId; document.getElementById('mro-proposta-ref').value = `${po.numero}`; document.getElementById('mro-modelo').value = cli?.drone || 'DJI Matrice 350 RTK'; document.getElementById('mro-sistema').value = po.produto || 'DRS-M350-PRO'; document.getElementById('mro-tipo-atendimento').value = 'Integração / instalação'; document.getElementById('mro-problema').value = `Integração e testes do pedido ${po.numero} (${po.produto}).`; document.getElementById('card-form-os-mro')?.scrollIntoView({ behavior: 'smooth' }); } function renderPosVendaPOClientes() { const container = document.getElementById('lista-pos-po-clientes'); if (!container) return; const termo = normalizarTexto(document.getElementById('filtro-pos-po-clientes')?.value || ''); const listaPOs = (db.pos || []).filter(p => { if (!termo) return true; const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); return normalizarTexto(`${p.numero} ${p.refVenda} ${cli?.nome || ''} ${cli?.drone || ''} ${p.produto}`).includes(termo); }); if (listaPOs.length === 0) { container.innerHTML = `<div class="p-6 text-center text-zinc-400 text-xs italic border rounded-xl bg-zinc-50">Nenhum P.O. ativo no momento.</div>`; return; } container.innerHTML = listaPOs.map(p => { const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)) || {}; const trk = p.tracking || {}; const defEtapa = obterDefEtapaTracking(trk.etapaCodigo); const prep = p.posVendaPrep || { statusPrep: 'Aguardando chegada p/ integração', observacaoPrep: '' }; const itensNecessarios = diagnosticarNecessidadesPosVendaPO(p, cli); return ` <div class="p-4 rounded-xl border border-zinc-200 bg-white shadow-sm space-y-3 text-xs"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <div class="flex flex-wrap items-center gap-2"> <span class="font-mono font-black text-xs bg-zinc-950 text-white px-2.5 py-1 rounded-lg">${p.numero}</span> <b class="text-zinc-900">${cli.nome || 'Cliente'}</b> <span class="text-zinc-500">• ${cli.contato || ''} (${cli.whatsapp || '-'})</span> <span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-sky-100 text-sky-900">${defEtapa.curto} • ETA: ${fmtDataBR(trk.eta || p.previsao)}</span> </div> <div class="flex gap-1.5"> <button type="button" onclick="prepararCadastroSNDePO('${p.numero}')" class="px-2.5 py-1 bg-zinc-900 hover:bg-zinc-700 text-white rounded-lg text-[11px] font-semibold"> + Cadastrar S.N. </button> <button type="button" onclick="abrirOSManutencaoDePO('${p.numero}')" class="px-2.5 py-1 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-[11px] font-bold"> + Abrir O.S. </button> </div> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-3 bg-zinc-50 p-3 rounded-lg border"> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Pedido & Aeronave do Cliente</span> <b>${p.produto}</b> (Drone: ${cli.drone || 'RPAS'} • Cidade: ${cli.cidade || '-'}) </div> <div> <span class="text-[10px] font-bold uppercase text-emerald-700 block">O que o cliente possivelmente vai precisar:</span> <div class="flex flex-wrap gap-1 mt-1"> ${itensNecessarios.map(it => `<span class="px-2 py-0.5 bg-emerald-50 border border-emerald-200 text-emerald-900 rounded text-[10px] font-semibold">• ${it}</span>`).join('')} </div> </div> </div> <div class="grid grid-cols-1 md:grid-cols-12 gap-2 items-center"> <select id="prep-status-${p.numero}" class="md:col-span-4 border rounded-lg px-2.5 py-1.5 bg-white font-semibold"> <option value="Aguardando chegada p/ integração" ${prep.statusPrep === 'Aguardando chegada p/ integração' ? 'selected' : ''}>⏳ Aguardando chegada p/ integração</option> <option value="Cliente contatado / Preparando kit" ${prep.statusPrep === 'Cliente contatado / Preparando kit' ? 'selected' : ''}>📞 Cliente contatado / Preparando kit</option> <option value="Instalação / Integração agendada" ${prep.statusPrep === 'Instalação / Integração agendada' ? 'selected' : ''}>📅 Instalação / Integração agendada</option> <option value="Entregue, Integrado e S.N. Ativo" ${prep.statusPrep === 'Entregue, Integrado e S.N. Ativo' ? 'selected' : ''}>✅ Entregue, Integrado e S.N. Ativo</option> </select> <input type="text" id="prep-obs-${p.numero}" value="${prep.observacaoPrep || ''}" placeholder="Observações do Pós-venda para este cliente..." class="md:col-span-6 border rounded-lg px-2.5 py-1.5 bg-white"> <button type="button" onclick="salvarStatusPreparacaoPOPosVenda('${p.numero}')" class="md:col-span-2 bg-zinc-900 hover:bg-zinc-700 text-white py-1.5 px-3 rounded-lg font-bold"> Salvar </button> </div> </div> `; }).join(''); } function aoSelecionarSNDemanda() { const snVal = document.getElementById('dem-sn')?.value; const snObj = (db.sns || []).find(s => s.sn === snVal); if (snObj) { const elCli = document.getElementById('dem-cliente'); if (elCli && snObj.clienteId) elCli.value = snObj.clienteId; const elHoras = document.getElementById('dem-horas'); if (elHoras) elHoras.value = snObj.horas || 0; } aoMudarTipoIntervencaoDemanda(); } function aoMudarTipoIntervencaoDemanda() { const tipo = document.getElementById('dem-tipo-intervencao')?.value; const hojeISO = new Date().toISOString().slice(0, 10); const elRev = document.getElementById('dem-nova-revisao'); const elPrazo = document.getElementById('dem-prazo'); if (elPrazo && !elPrazo.value) elPrazo.value = hojeISO; if (elRev && ['ACIONAMENTO_EQUIPAMENTO', 'MANUTENCAO_PREVENTIVA', 'MANUTENCAO_PERIODICA'].includes(tipo)) { elRev.value = adicionarMesesData(hojeISO, 12); } } function salvarDemandaPosVenda(e) { e.preventDefault(); garantirEstruturaDB(); const protocolo = 'PROT-' + Date.now().toString().slice(-5); const clienteId = Number(document.getElementById('dem-cliente').value); const sn = document.getElementById('dem-sn').value; const tipoIntervencao = document.getElementById('dem-tipo-intervencao').value; const horasAtuais = parseFloat(document.getElementById('dem-horas').value) || 0; const novaRevisao = document.getElementById('dem-nova-revisao').value; const prioridade = document.getElementById('dem-prioridade').value; const prazo = document.getElementById('dem-prazo').value || new Date().toISOString().slice(0, 10); const estItemId = document.getElementById('dem-estoque-item')?.value || ''; const estQtd = parseInt(document.getElementById('dem-estoque-qtd')?.value) || 0; const abrirOSAuto = !!document.getElementById('dem-gerar-os-auto')?.checked; const desc = document.getElementById('dem-desc').value.trim(); const status = document.getElementById('dem-status').value; const agora = carimboHoraAtual(); const hojeISO = new Date().toISOString().slice(0, 10); const mapaLabelsTipo = { 'ACIONAMENTO_EQUIPAMENTO': 'Acionamento de Equipamento', 'MANUTENCAO_PREVENTIVA': 'Manutenção Preventiva', 'MANUTENCAO_PERIODICA': 'Manutenção Periódica (12m)', 'MANUTENCAO_CORRETIVA': 'Manutenção Corretiva', 'INTEGRACAO_INSTALACAO': 'Integração / Instalação' }; const labelCategoria = mapaLabelsTipo[tipoIntervencao] || tipoIntervencao; const integracoesExecutadas = []; const snObj = db.sns.find(s => s.sn === sn); if (snObj) { if (horasAtuais > Number(snObj.horas || 0)) snObj.horas = horasAtuais; if (tipoIntervencao === 'ACIONAMENTO_EQUIPAMENTO') { snObj.acionamentos = (Number(snObj.acionamentos) || 0) + 1; integracoesExecutadas.push(`+1 Acionamento (${snObj.acionamentos})`); } if (novaRevisao && ['ACIONAMENTO_EQUIPAMENTO', 'MANUTENCAO_PREVENTIVA', 'MANUTENCAO_PERIODICA'].includes(tipoIntervencao)) { snObj.revisao = novaRevisao; integracoesExecutadas.push(`Revisão: ${fmtDataBR(novaRevisao)}`); } snObj.modificadoPor = sessaoAtual.nome; snObj.modificadoEm = agora; } const tipoLogbook = tipoIntervencao === 'ACIONAMENTO_EQUIPAMENTO' ? 'Acionamento' : tipoIntervencao === 'MANUTENCAO_PERIODICA' ? 'Revisão Programada (12 Meses)' : tipoIntervencao === 'MANUTENCAO_PREVENTIVA' ? 'Manutenção Preventiva' : 'Manutenção Corretiva'; db.logbook.unshift({ id: Date.now(), sn, tipo: tipoLogbook, origemHoras: `Demanda ${protocolo}`, data: hojeISO, horas: horasAtuais, desc: `[${protocolo} • ${labelCategoria}] ${desc}`, tecnico: sessaoAtual.nome, criadoPor: sessaoAtual.nome, criadoEm: agora }); integracoesExecutadas.push('Logbook'); if (tipoIntervencao === 'INTEGRACAO_INSTALACAO') { db.integracoes.unshift({ id: Date.now() + 1, sn, responsavel: sessaoAtual.nome, data: hojeISO, status: 'Em integração', testes: `[${protocolo}] ${desc}`, criadoPor: sessaoAtual.nome, criadoEm: agora }); integracoesExecutadas.push('Integração'); } if (estItemId && estQtd > 0) { const itemEst = db.estoque.find(x => x.id === estItemId); if (itemEst && itemEst.qtd >= estQtd) { itemEst.qtd -= estQtd; const valorImpacto = Number((estQtd * Number(itemEst.custoUnit || 0)).toFixed(2)); const movId = 'MOV-' + Date.now().toString().slice(-5); db.movimentacoesEstoque.unshift({ id: movId, itemId: itemEst.id, pn: itemEst.pn, lote: itemEst.lote || '-', sn: itemEst.sn || '-', tipo: 'SAIDA_MRO', qtd: -estQtd, valorImpacto: -valorImpacto, motivo: `Baixa p/ Demanda ${protocolo} (S.N. ${sn})`, data: hojeISO, usuario: sessaoAtual.nome, dataHora: agora }); if (valorImpacto > 0) { registrarReflexoContabilEstoque(movId, 'CPV_CMV', valorImpacto, `Consumo MRO ${protocolo} (${estQtd}x ${itemEst.pn})`, `${protocolo}`, hojeISO); } integracoesExecutadas.push(`Estoque (-${estQtd} ${itemEst.pn})`); } } let osGeradaId = null; if (abrirOSAuto) { const cliObj = db.clientes.find(c => Number(c.id) === Number(clienteId)) || {}; osGeradaId = 'OS-' + Date.now().toString().slice(-4); db.ordensServicoMRO.unshift({ id: osGeradaId, abertura: hojeISO, encerramento: prazo, prioridade: prioridade.includes('Crítica') ? 'Crítica' : prioridade, unidade: 'Oficina Central LES • Anápolis-GO', propostaRef: `Demanda ${protocolo}`, clienteId, equipamento: 'Aeronave RPAS / Sistema', fabricanteModelo: cliObj.drone || 'DJI Matrice 350 RTK', snEquipamento: snObj ? snObj.snAnv : 'ANV-CLIENTE', sistemaInstalado: snObj ? snObj.pn : 'DRS-M350-PRO', snSistema: sn, garantia: 'Conforme contrato', tipoAtendimento: tipoIntervencao === 'ACIONAMENTO_EQUIPAMENTO' || tipoIntervencao === 'MANUTENCAO_CORRETIVA' ? 'Corretiva' : 'Preventiva', tecnico: sessaoAtual.nome, coordenador: 'Matheus Azevedo', statusFluxo: '1. OS Aberta / Recebida', problema: `[${labelCategoria}] ${desc}`, acessorios: 'Aeronave e sistema S.N. ' + sn, diagnostico: `Análise técnica referente a ${labelCategoria} (${horasAtuais}h).`, atividades: desc, pecasHoras: `${horasAtuais}h registradas`, testesExpedicao: 'Aprovado em bancada.', criadoPor: sessaoAtual.nome, criadoEm: agora }); integracoesExecutadas.push(`Gerou ${osGeradaId}`); } db.demandasPosVenda.unshift({ protocolo, clienteId, sn, tipoIntervencao, categoria: labelCategoria, horas: horasAtuais, novaRevisao: novaRevisao || '-', prioridade, prazo, desc, status, osVinculada: osGeradaId, integracoesResumo: integracoesExecutadas.join(' • '), criadoPor: sessaoAtual.nome, criadoEm: agora }); registrarAuditoria('POS_VENDA', `Registrou Demanda ${protocolo} (${labelCategoria})`, protocolo); e.target.reset(); saveDB(); } // ========================================================= // 3. ORDENS DE SERVIÇO (O.S.) COM EDIÇÃO COMPLETA E DOCUMENTO UNIFICADO // ========================================================= function salvarOrdemServicoMRO(e) { e.preventDefault(); garantirEstruturaDB(); const editId = document.getElementById('mro-edit-id').value.trim(); const numeroOS = document.getElementById('mro-numero-os').value.trim(); const agora = carimboHoraAtual(); const dadosOS = { id: numeroOS, abertura: document.getElementById('mro-abertura').value || new Date().toISOString().slice(0, 10), encerramento: document.getElementById('mro-abertura').value || new Date().toISOString().slice(0, 10), prioridade: document.getElementById('mro-prioridade').value, unidade: document.getElementById('mro-unidade')?.value || 'Oficina Central LES', propostaRef: document.getElementById('mro-proposta-ref').value.trim() || 'Conforme Contrato', clienteId: Number(document.getElementById('mro-cliente').value), equipamento: document.getElementById('mro-equipamento')?.value || 'Drone / RPAS', fabricanteModelo: document.getElementById('mro-modelo').value.trim(), snEquipamento: document.getElementById('mro-sn-eq').value.trim(), sistemaInstalado: document.getElementById('mro-sistema').value.trim(), snSistema: document.getElementById('mro-sn-sistema').value.trim() || 'N/A', garantia: document.getElementById('mro-garantia')?.value || '90 dias', tipoAtendimento: document.getElementById('mro-tipo-atendimento').value, tecnico: document.getElementById('mro-tecnico').value.trim(), coordenador: document.getElementById('mro-coordenador')?.value || 'Matheus Azevedo', statusFluxo: document.getElementById('mro-status-fluxo').value, problema: document.getElementById('mro-problema').value.trim(), acessorios: document.getElementById('mro-acessorios')?.value || 'Case rígido, baterias e acessórios padrão', diagnostico: document.getElementById('mro-diagnostico').value.trim(), atividades: document.getElementById('mro-atividades').value.trim(), pecasHoras: document.getElementById('mro-pecas-horas')?.value || 'Conforme escopo executado', testesExpedicao: document.getElementById('mro-testes-expedicao').value.trim() }; if (editId) { const idx = db.ordensServicoMRO.findIndex(o => String(o.id) === String(editId)); if (idx >= 0) { db.ordensServicoMRO[idx] = { ...db.ordensServicoMRO[idx], ...dadosOS, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('POS_VENDA', `Editou Ordem de Serviço ${numeroOS} (${dadosOS.statusFluxo})`, numeroOS); cancelarEdicaoOSMRO(); saveDB(); return; } } if (db.ordensServicoMRO.some(o => String(o.id) === String(numeroOS))) { alert('Já existe uma O.S. com este número. Use o botão "Editar O.S." na lista abaixo para alterá-la.'); return; } db.ordensServicoMRO.unshift({ ...dadosOS, criadoPor: sessaoAtual.nome, criadoEm: agora }); if (dadosOS.snSistema && dadosOS.snSistema !== 'N/A') { db.logbook.unshift({ id: Date.now(), sn: dadosOS.snSistema, tipo: dadosOS.tipoAtendimento === 'Preventiva' ? 'Manutenção Preventiva' : 'Manutenção Corretiva', origemHoras: `O.S. ${numeroOS}`, data: dadosOS.abertura, horas: 0, desc: `[${numeroOS}] ${dadosOS.problema} — ${dadosOS.atividades}`, tecnico: dadosOS.tecnico, criadoPor: sessaoAtual.nome, criadoEm: agora }); } registrarAuditoria('POS_VENDA', `Abriu Ordem de Serviço ${numeroOS}`, numeroOS); cancelarEdicaoOSMRO(); saveDB(); } function editarOSMRO(osId) { const os = (db.ordensServicoMRO || []).find(o => String(o.id) === String(osId)); if (!os) return; switchSubTab('pos', 'mro_drones'); document.getElementById('mro-edit-id').value = os.id; document.getElementById('mro-numero-os').value = os.id; document.getElementById('mro-abertura').value = os.abertura || ''; document.getElementById('mro-prioridade').value = os.prioridade || 'Normal'; document.getElementById('mro-cliente').value = os.clienteId; document.getElementById('mro-modelo').value = os.fabricanteModelo || ''; document.getElementById('mro-sn-eq').value = os.snEquipamento || ''; document.getElementById('mro-sistema').value = os.sistemaInstalado || ''; document.getElementById('mro-sn-sistema').value = os.snSistema || ''; document.getElementById('mro-tipo-atendimento').value = os.tipoAtendimento || 'Preventiva'; document.getElementById('mro-tecnico').value = os.tecnico || ''; document.getElementById('mro-proposta-ref').value = os.propostaRef || ''; document.getElementById('mro-status-fluxo').value = os.statusFluxo || '1. OS Aberta / Recebida'; document.getElementById('mro-problema').value = os.problema || ''; document.getElementById('mro-diagnostico').value = os.diagnostico || ''; document.getElementById('mro-atividades').value = os.atividades || ''; document.getElementById('mro-testes-expedicao').value = os.testesExpedicao || ''; const badge = document.getElementById('mro-mode-badge'); if (badge) badge.textContent = `MODO DE EDIÇÃO ATIVO • ${os.id}`; document.getElementById('mro-form-titulo').textContent = `Editando Ordem de Serviço ${os.id}`; document.getElementById('btn-submit-mro').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5"></i> Salvar Alterações na ${os.id}`; document.getElementById('btn-cancelar-edicao-mro').classList.remove('hidden'); document.getElementById('card-form-os-mro')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoOSMRO() { const form = document.getElementById('form-os-mro'); if (form) form.reset(); document.getElementById('mro-edit-id').value = ''; const badge = document.getElementById('mro-mode-badge'); if (badge) badge.textContent = 'Manutenção de Drones & Sistemas (MRO)'; document.getElementById('mro-form-titulo').textContent = 'Nova Ordem de Serviço (O.S.) / Editar O.S.'; document.getElementById('btn-submit-mro').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5"></i> Salvar Ordem de Serviço (O.S.)`; document.getElementById('btn-cancelar-edicao-mro').classList.add('hidden'); } function renderOrdensServicoMRO() { const container = document.getElementById('lista-os-mro-cards'); if (!container) return; const lista = db.ordensServicoMRO || []; if (lista.length === 0) { container.innerHTML = `<div class="p-6 text-center text-zinc-400 text-xs italic border rounded-xl">Nenhuma Ordem de Serviço (O.S.) cadastrada.</div>`; return; } container.innerHTML = lista.map(os => { const cli = db.clientes.find(c => Number(c.id) === Number(os.clienteId)) || {}; return ` <div class="p-4 rounded-xl border border-zinc-200 bg-white hover:border-zinc-300 shadow-sm space-y-3 text-xs"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <div class="flex flex-wrap items-center gap-2"> <span class="font-mono font-black text-xs bg-zinc-950 text-white px-2.5 py-1 rounded-lg">${os.id}</span> <b class="text-zinc-950">${cli.nome || 'Cliente'}</b> <span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-emerald-100 text-emerald-900">${os.statusFluxo}</span> <span class="text-zinc-500">• ${os.fabricanteModelo} (S/N: <b class="font-mono">${os.snEquipamento}</b>) • Sist: <b class="font-mono">${os.snSistema}</b></span> </div> <div class="flex flex-wrap gap-1.5"> <button type="button" onclick="editarOSMRO('${os.id}')" class="bg-sky-600 hover:bg-sky-700 text-white px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-pen-to-square mr-1"></i> Editar O.S. </button> <button type="button" onclick="gerarDocumentoOficialMRO('${os.id}', 'OS_UNICA')" class="bg-zinc-950 hover:bg-zinc-800 text-white px-3.5 py-1.5 rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-file-pdf text-rose-400 mr-1"></i> PDF da O.S. (Consolidado) </button> <button type="button" onclick="gerarDocumentoOficialMRO('${os.id}', 'TERMO_ENTREGA')" class="bg-emerald-600 hover:bg-emerald-700 text-white px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-file-circle-check mr-1"></i> Termo de Entrega </button> </div> </div> <div class="grid grid-cols-1 md:grid-cols-3 gap-3 bg-zinc-50 p-3 rounded-lg border text-[11px]"> <div><b class="text-zinc-700 block">1. Problema / Solicitação:</b> ${os.problema}</div> <div><b class="text-zinc-700 block">2. Diagnóstico & Serviço Executado:</b> ${os.diagnostico} — ${os.atividades}</div> <div><b class="text-zinc-700 block">3. Testes & Responsável:</b> ${os.testesExpedicao} (${os.tecnico})</div> </div> </div> `; }).join(''); } function gerarDocumentoOficialMRO(osId, tipoDoc = 'OS_UNICA') { const os = (db.ordensServicoMRO || []).find(o => String(o.id) === String(osId)); if (!os) return; const cli = db.clientes.find(c => Number(c.id) === Number(os.clienteId)) || {}; const dataAb = fmtDataBR(os.abertura); const dataEnc = fmtDataBR(os.encerramento || os.abertura); const ehTermo = tipoDoc === 'TERMO_ENTREGA'; const tituloDoc = ehTermo ? 'TERMO DE ENCERRAMENTO, EXPEDIÇÃO E ENTREGA TÉCNICA' : 'ORDEM DE SERVIÇO E LAUDO TÉCNICO DE MANUTENÇÃO (MRO)'; const codDoc = ehTermo ? 'LES-MAN-TERMO-UNICO' : 'LES-MAN-OS-CONSOLIDADA'; const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>${os.id} — ${tituloDoc}</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 14mm 16mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } .sec-title { background: #09090b; color: #ffffff; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; padding: 6px 10px; margin-top: 14px; margin-bottom: 6px; border-radius: 4px; } .grid-table { width: 100%; border-collapse: collapse; font-size: 11px; } .grid-table td { border: 1px solid #d4d4d8; padding: 8px 10px; vertical-align: top; line-height: 1.45; } .lbl { display: block; font-size: 8.5px; font-weight: 800; text-transform: uppercase; color: #71717a; margin-bottom: 3px; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES AVIATION MRO</b> • ${os.id} (${tituloDoc})</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Imprimir / Salvar PDF</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:12px;"> ${obterLogoHTMLDocumento('CENTRO DE MANUTENÇÃO AERONÁUTICA DE DRONES E SISTEMAS (MRO)')} <div style="text-align:right;"> <div style="font-size:14px; font-weight:900; text-transform:uppercase;">${tituloDoc}</div> <div style="font-size:11px; font-family:monospace; font-weight:800; margin-top:5px; background:#f4f4f5; padding:3px 8px; border-radius:4px; border:1px solid #d4d4d8; display:inline-block;"> Nº ${os.id} | DATA: ${dataAb} | ${codDoc} </div> </div> </div> <div class="sec-title">01 IDENTIFICAÇÃO DO CLIENTE E EQUIPAMENTO (DRONE / SISTEMA)</div> <table class="grid-table"> <tr> <td><span class="lbl">CLIENTE / RAZÃO SOCIAL</span><b>${cli.nome || '-'} (${cli.cnpj || '-'})</b></td> <td><span class="lbl">CONTATO / TELEFONE</span><b>${cli.contato || '-'} • ${cli.whatsapp || '-'}</b></td> <td><span class="lbl">TIPO DE ATENDIMENTO</span><b>${os.tipoAtendimento} (${os.prioridade})</b></td> </tr> <tr> <td><span class="lbl">MODELO DO DRONE / RPAS</span><b>${os.fabricanteModelo}</b></td> <td><span class="lbl">S/N DO DRONE</span><b style="font-family:monospace;">${os.snEquipamento}</b></td> <td><span class="lbl">SISTEMA / S.N.</span><b style="font-family:monospace;">${os.sistemaInstalado} (${os.snSistema})</b></td> </tr> </table> <div class="sec-title">02 SOLICITAÇÃO, DIAGNÓSTICO TÉCNICO E ESCOPO EXECUTADO</div> <table class="grid-table"> <tr><td><span class="lbl">PROBLEMA / SOLICITAÇÃO DO CLIENTE</span>${os.problema}</td></tr> <tr><td><span class="lbl">DIAGNÓSTICO TÉCNICO</span>${os.diagnostico}</td></tr> <tr><td><span class="lbl">SERVIÇOS EXECUTADOS E COMPONENTES APLICADOS</span><b>${os.atividades}</b></td></tr> </table> <div class="sec-title">03 BATERIA DE TESTES, QUALIDADE E CONDIÇÃO DE ENTREGA</div> <table class="grid-table"> <tr> <td colspan="2"><span class="lbl">RESULTADO DOS TESTES FINAIS E CRITÉRIOS DE ACEITE</span><b>${os.testesExpedicao}</b></td> </tr> <tr> <td><span class="lbl">STATUS ATUAL DA O.S.</span><b>${os.statusFluxo}</b></td> <td><span class="lbl">GARANTIA / REFERÊNCIA</span><b>${os.propostaRef || '90 dias sobre o serviço executado'}</b></td> </tr> </table> </div> <div style="margin-top:24px;"> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; margin-bottom:14px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>RESPONSÁVEL TÉCNICO LES AVIATION</b><br>${os.tecnico} • Coord.: ${os.coordenador} </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>CIÊNCIA E RECEBIMENTO DO CLIENTE</b><br>${cli.contato || cli.nome || 'Representante Autorizado'} — Data: ___/___/______ </div> </div> <div style="border-top:2px solid #09090b; padding-top:8px; display:flex; justify-content:space-between; font-size:9.5px; color:#52525b; font-weight:600;"> <span>LES AVIATION MANAGEMENT SYSTEM • ${os.id}</span> <span>Emissão: ${dataEnc}</span> </div> </div> </div> </body> </html> `); win.document.close(); registrarAuditoria('POS_VENDA', `Emitiu PDF consolidado [${tipoDoc}] da ${os.id}`, os.id); saveDB(); } // ========================================================= // MANUAIS TÉCNICOS HOMOLOGADOS: CADASTRO, VISUALIZADOR MODAL E BLOB URL // ========================================================= function salvarManual(e) { e.preventDefault(); const f = document.getElementById('man-pdf'); if (f && f.files.length) { const arq = f.files[0]; const r = new FileReader(); r.onload = evt => gravarManual(evt.target.result, arq.name); r.readAsDataURL(arq); } else { gravarManual('', ''); } } function gravarManual(pdf, nomeArq = '') { const pn = document.getElementById('man-pn').value; const versao = document.getElementById('man-versao').value.trim(); const titulo = document.getElementById('man-titulo').value.trim(); const data = document.getElementById('man-data').value; const obs = document.getElementById('man-obs').value.trim(); const idNovo = Date.now(); if (pdf && pdf.startsWith('data:')) { salvarArquivoNoIndexedDB(`MAN_PDF_${idNovo}`, pdf); } db.manuais.unshift({ id: idNovo, pn, titulo, versao, data, obs, pdf, nomeArquivo: nomeArq || `Manual_${pn}_${versao.replace(/\s+/g, '_')}.pdf`, aprovadoDiretoria: false, criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); registrarAuditoria('POS_VENDA', `Cadastrou manual ${versao} (${titulo})`, pn); const formMan = document.getElementById('man-versao')?.closest('form'); if (formMan) formMan.reset(); saveDB(); } function alternarAprovacaoDiretoria(id) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Homologar Manual (Diretoria Drones / Holding)')) return; const m = db.manuais.find(x => Number(x.id) === Number(id)); if (m) { m.aprovadoDiretoria = !m.aprovadoDiretoria; m.homologadoPor = m.aprovadoDiretoria ? `${sessaoAtual.nome} (${carimboHoraAtual()})` : null; registrarAuditoria('DIRETORIA', `${m.aprovadoDiretoria ? 'Homologou' : 'Revogou'} manual ${m.versao}`, m.pn); saveDB(); } } function gerarDocumentoHTMLManualHomologado(m) { const pnObj = db.pns.find(p => p.pn === m.pn) || {}; const statusTexto = m.aprovadoDiretoria ? `HOMOLOGADO PELA DIRETORIA (${m.homologadoPor || 'Diretoria LES Aviation'})` : 'EM REVISÃO / PENDENTE DE HOMOLOGAÇÃO DA DIRETORIA'; const corStatus = m.aprovadoDiretoria ? '#047857' : '#b45309'; return ` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Manual Técnico ${m.pn} — ${m.versao} — LES AVIATION</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 285mm; background: #ffffff; margin: 0 auto; padding: 16mm 18mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } .sec-title { background: #09090b; color: #ffffff; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; padding: 6px 10px; margin-top: 16px; margin-bottom: 8px; border-radius: 4px; } .grid-table { width: 100%; border-collapse: collapse; font-size: 11.5px; } .grid-table td { border: 1px solid #d4d4d8; padding: 9px 12px; vertical-align: top; line-height: 1.5; } .lbl { display: block; font-size: 8.5px; font-weight: 800; text-transform: uppercase; color: #71717a; margin-bottom: 3px; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES AVIATION • PUBLICAÇÃO TÉCNICA</b> • ${m.pn} (${m.versao})</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Imprimir / Salvar PDF do Manual</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:14px; margin-bottom:16px;"> ${obterLogoHTMLDocumento('PUBLICAÇÃO TÉCNICA AERONÁUTICA & PROCEDIMENTOS MRO')} <div style="text-align:right;"> <div style="font-size:15px; font-weight:900; text-transform:uppercase;">MANUAL TÉCNICO E BOLETIM DE REVISÃO</div> <div style="font-size:11px; font-family:monospace; font-weight:800; margin-top:5px; background:#f4f4f5; padding:4px 10px; border-radius:4px; border:1px solid #d4d4d8; display:inline-block;"> P/N: ${m.pn} | VERSÃO: ${m.versao} | DATA: ${fmtDataBR(m.data)} </div> </div> </div> <div style="padding:10px 14px; border-radius:6px; border:2px solid ${corStatus}; background:${m.aprovadoDiretoria ? '#ecfdf5' : '#fffbeb'}; color:${corStatus}; font-size:11px; font-weight:800; margin-bottom:14px;"> STATUS DE GOVERNANÇA TÉCNICA: ${statusTexto} </div> <div class="sec-title">01 IDENTIFICAÇÃO DO EQUIPAMENTO E PUBLICAÇÃO TÉCNICA</div> <table class="grid-table"> <tr> <td><span class="lbl">PART NUMBER (P/N)</span><b style="font-family:monospace; font-size:13px;">${m.pn}</b></td> <td><span class="lbl">EQUIPAMENTO / SISTEMA VINCULADO</span><b>${pnObj.desc || 'Sistema Aeronáutico LES Aviation'}</b></td> <td><span class="lbl">SHELF LIFE / CICLO DE REVISÃO</span><b>Shelf: ${pnObj.shelf || 24} meses • Revisão: 12 meses</b></td> </tr> <tr> <td colspan="2"><span class="lbl">TÍTULO OFICIAL DO MANUAL TÉCNICO</span><b style="font-size:13px;">${m.titulo}</b></td> <td><span class="lbl">REVISÃO / EMISSÃO</span><b>${m.versao} (${fmtDataBR(m.data)})</b></td> </tr> </table> <div class="sec-title">02 ESCOPO TÉCNICO, NOTAS DE REVISÃO E DIRETRIZES DE MANUTENÇÃO</div> <table class="grid-table"> <tr> <td style="min-height:140px;"> <span class="lbl">DESCRIÇÃO / NOTAS DE REVISÃO E PROCEDIMENTOS APLICÁVEIS</span> <div style="font-size:12px; line-height:1.65; margin-top:6px; white-space:pre-line;">${m.obs || 'Procedimento técnico aplicável conforme padrões do fabricante e diretrizes do Centro de Manutenção Aeronáutica LES Aviation.'}</div> </td> </tr> </table> <div class="sec-title">03 CONTROLE DE RASTREABILIDADE E HOMOLOGAÇÃO EXECUTIVA</div> <table class="grid-table"> <tr> <td><span class="lbl">ELABORADO / CADASTRADO POR</span><b>${m.criadoPor || 'Engenharia MRO'} (${m.criadoEm || fmtDataBR(m.data)})</b></td> <td><span class="lbl">HOMOLOGAÇÃO DA DIRETORIA</span><b>${m.aprovadoDiretoria ? (m.homologadoPor || 'Aprovado pela Diretoria') : 'Aguardando Homologação'}</b></td> </tr> </table> </div> <div style="margin-top:24px;"> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; margin-bottom:14px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>ENGENHARIA & MANUTENÇÃO (MRO)</b><br>Responsável Técnico: ${m.criadoPor || 'Equipe MRO LES'} </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>DIRETORIA EXECUTIVA LES AVIATION</b><br>${m.aprovadoDiretoria ? `Homologado por: ${m.homologadoPor}` : 'Assinatura de Homologação'} </div> </div> <div style="border-top:2px solid #09090b; padding-top:8px; display:flex; justify-content:space-between; font-size:9.5px; color:#52525b; font-weight:600;"> <span>LES AVIATION MANAGEMENT SYSTEM • REPOSITÓRIO OFICIAL DE MANUAIS TÉCNICOS</span> <span>P/N ${m.pn} • ${m.versao}</span> </div> </div> </div> </body> </html> `; } function abrirModalVisualizarManual(idManual) { const m = (db.manuais || []).find(x => Number(x.id) === Number(idManual)); if (!m) return; // Caso o PDF estivesse salvo no IndexedDB, recupera-o imediatamente para visualização if ((!m.pdf || m.pdf === 'IDB_STORED') && cacheArquivosMemoria[`MAN_PDF_${m.id}`]) { m.pdf = cacheArquivosMemoria[`MAN_PDF_${m.id}`]; } if (blobUrlManualAtivo) { try { URL.revokeObjectURL(blobUrlManualAtivo); } catch (e) {} blobUrlManualAtivo = null; } const elStatus = document.getElementById('modal-man-status-badge'); const elTit = document.getElementById('modal-man-titulo'); const elSub = document.getElementById('modal-man-subtitulo'); const elMeta = document.getElementById('modal-man-metadados'); const elViewer = document.getElementById('modal-man-viewer-container'); const btnNovaAba = document.getElementById('btn-modal-man-nova-aba'); const btnDownload = document.getElementById('btn-modal-man-download'); const inputUpdPdf = document.getElementById('input-modal-man-update-pdf'); if (elStatus) { elStatus.textContent = m.aprovadoDiretoria ? '✓ MANUAL HOMOLOGADO PELA DIRETORIA' : '⏳ PENDENTE DE HOMOLOGAÇÃO DA DIRETORIA'; elStatus.className = 'px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase inline-block mb-1 ' + (m.aprovadoDiretoria ? 'bg-emerald-600 text-white' : 'bg-amber-100 text-amber-900'); } if (elTit) elTit.textContent = `${m.titulo} — P/N ${m.pn} (${m.versao})`; if (elSub) elSub.textContent = `Cadastrado por ${m.criadoPor || 'Sistema'} em ${m.criadoEm || fmtDataBR(m.data)} ${m.homologadoPor ? `• Homologado por: ${m.homologadoPor}` : ''}`; if (elMeta) { elMeta.innerHTML = ` <div><span class="text-[10px] font-bold uppercase text-zinc-400 block">Part Number (P/N)</span><b class="font-mono text-zinc-950">${m.pn}</b></div> <div><span class="text-[10px] font-bold uppercase text-zinc-400 block">Versão / Revisão</span><b class="font-mono text-zinc-950">${m.versao} (${fmtDataBR(m.data)})</b></div> <div class="md:col-span-2"><span class="text-[10px] font-bold uppercase text-zinc-400 block">Notas de Revisão</span><span class="text-zinc-800 font-medium">${m.obs || '—'}</span></div> `; } if (inputUpdPdf) { inputUpdPdf.onchange = (ev) => { const arq = ev.target.files[0]; if (!arq) return; const r = new FileReader(); r.onload = (loadEv) => { m.pdf = loadEv.target.result; m.nomeArquivo = arq.name; m.modificadoPor = sessaoAtual.nome; m.modificadoEm = carimboHoraAtual(); salvarArquivoNoIndexedDB(`MAN_PDF_${m.id}`, m.pdf); registrarAuditoria('POS_VENDA', `Anexou/Atualizou arquivo PDF (${arq.name}) no manual ${m.versao}`, m.pn); saveDB(); abrirModalVisualizarManual(m.id); }; r.readAsDataURL(arq); }; } const temPdfAnexo = !!(m.pdf && m.pdf.length > 30 && m.pdf !== 'IDB_STORED'); if (temPdfAnexo) { blobUrlManualAtivo = converterDataUrlParaBlobUrl(m.pdf); const ehImagem = m.pdf.startsWith('data:image/'); if (elViewer) { if (ehImagem) { elViewer.innerHTML = `<img src="${blobUrlManualAtivo || m.pdf}" alt="${m.titulo}" class="max-h-[65vh] w-auto object-contain mx-auto p-4">`; } else if (blobUrlManualAtivo) { elViewer.innerHTML = `<iframe src="${blobUrlManualAtivo}" class="w-full h-[65vh] border-0 bg-white" title="${m.titulo}"></iframe>`; } else { elViewer.innerHTML = `<div class="p-8 text-center text-xs text-zinc-600">Arquivo validado. Clique em <b>"Abrir / Imprimir em Nova Guia"</b> acima para visualizar.</div>`; } } if (btnNovaAba) { btnNovaAba.onclick = () => { const u = converterDataUrlParaBlobUrl(m.pdf); if (u) window.open(u, '_blank'); }; } if (btnDownload) { btnDownload.onclick = () => { const u = converterDataUrlParaBlobUrl(m.pdf) || m.pdf; const a = document.createElement('a'); a.href = u; a.download = m.nomeArquivo || `Manual_${m.pn}_${m.versao.replace(/\s+/g, '_')}.pdf`; document.body.appendChild(a); a.click(); a.remove(); }; } } else { const htmlDoc = gerarDocumentoHTMLManualHomologado(m); const blobHtml = new Blob([htmlDoc], { type: 'text/html;charset=utf-8' }); blobUrlManualAtivo = URL.createObjectURL(blobHtml); if (elViewer) { elViewer.innerHTML = `<iframe src="${blobUrlManualAtivo}" class="w-full h-[65vh] border-0 bg-white" title="${m.titulo}"></iframe>`; } if (btnNovaAba) { btnNovaAba.onclick = () => { const win = window.open('', '_blank'); if (win) { win.document.write(gerarDocumentoHTMLManualHomologado(m)); win.document.close(); } }; } if (btnDownload) { btnDownload.onclick = () => { const win = window.open('', '_blank'); if (win) { win.document.write(gerarDocumentoHTMLManualHomologado(m)); win.document.close(); setTimeout(() => { try { win.print(); } catch (e) {} }, 350); } }; } } document.getElementById('modal-visualizar-manual').classList.remove('hidden'); } function fecharModalVisualizarManual() { document.getElementById('modal-visualizar-manual').classList.add('hidden'); const elViewer = document.getElementById('modal-man-viewer-container'); if (elViewer) elViewer.innerHTML = ''; if (blobUrlManualAtivo) { try { URL.revokeObjectURL(blobUrlManualAtivo); } catch (e) {} blobUrlManualAtivo = null; } } function salvarFechamentoPeriodo() { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Fechar Período Contábil (LES Aviation)')) return; db.financeiro.periodoFechado = document.getElementById('fin-periodo-fechado').value; saveDB(); } function salvarLancamentoFinanceiro(e) { e.preventDefault(); if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Registrar Lançamento Financeiro (LES Aviation)')) return; const comp = document.getElementById('fin-competencia').value; if (db.financeiro.periodoFechado && comp.slice(0, 7) <= db.financeiro.periodoFechado) { alert('Período contábil fechado.'); return; } const bruto = parseFloat(document.getElementById('fin-bruto').value) || 0; const impostos = parseFloat(document.getElementById('fin-impostos').value) || 0; const ajuste = parseFloat(document.getElementById('fin-ajuste').value) || 0; const id = 'FIN-' + Date.now().toString().slice(-5); db.financeiro.lancamentos.unshift({ id, natureza: document.getElementById('fin-natureza').value, categoria: document.getElementById('fin-categoria').value, desc: document.getElementById('fin-desc').value.trim(), cc: document.getElementById('fin-cc').value, entidade: document.getElementById('fin-entidade').value.trim(), doc: document.getElementById('fin-doc').value.trim(), competencia: comp, vencimento: document.getElementById('fin-vencimento').value, bruto, impostos, ajuste, liquido: bruto - impostos + ajuste, status: document.getElementById('fin-status').value, conta: document.getElementById('fin-conta').value.trim(), conciliado: false, estornado: false, criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); registrarAuditoria('FINANCEIRO', `Lançou registro financeiro ${id}`, id); e.target.reset(); saveDB(); } function conciliarLancamento(id) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Conciliar Lançamento (LES Aviation)')) return; const l = db.financeiro.lancamentos.find(x => x.id === id); if (l && !l.estornado) { l.conciliado = !l.conciliado; if (l.conciliado) l.status = 'REALIZADO'; registrarAuditoria('FINANCEIRO', `Conciliou ${id}`, id); saveDB(); } } function estornarLancamento(id) { if (!exigirPermissao(['diretoria', 'diretor_drones'], 'Estornar Lançamento (LES Aviation)')) return; const l = db.financeiro.lancamentos.find(x => x.id === id); if (l && !l.estornado) { l.estornado = true; l.status = 'ESTORNADO'; registrarAuditoria('FINANCEIRO', `Estornou ${id}`, id); saveDB(); } } function salvarContaContabil(e) { e.preventDefault(); db.financeiro.planoContas.push({ codigo: document.getElementById('pc-codigo').value.trim(), nome: document.getElementById('pc-nome').value.trim(), grupo: document.getElementById('pc-grupo').value }); e.target.reset(); saveDB(); } function salvarOrcamento() { db.financeiro.orcamento.metaReceita = parseFloat(document.getElementById('orc-meta-receita').value) || 0; db.financeiro.orcamento.limiteDespesa = parseFloat(document.getElementById('orc-limite-despesa').value) || 0; saveDB(); } function calcularValorContabilTotalEstoqueDefense() { return (db.defense?.estoque || []).reduce((acc, item) => acc + (Number(item.qtd || 0) * Number(item.custoUnit || 0)), 0); } function calcularDemonstrativosPorLista(lancamentosLista, valorEstoqueAtivo) { const validos = (lancamentosLista || []).filter(l => !l.estornado); const sum = f => validos.filter(f).reduce((a, b) => a + Number(b.liquido || 0), 0); const sumBruto = f => validos.filter(f).reduce((a, b) => a + Number(b.bruto || 0), 0); const sumImp = f => validos.filter(f).reduce((a, b) => a + Number(b.impostos || 0), 0); const receitaBruta = sumBruto(l => l.natureza === 'RECEITA'); const deducoesImpostos = sumImp(l => l.natureza === 'RECEITA'); const receitaLiquida = receitaBruta - deducoesImpostos; const cpv = sum(l => l.natureza === 'CPV_CMV'); const lucroBruto = receitaLiquida - cpv; const despesasOp = sum(l => l.natureza === 'DESPESA_OP'); const ebitda = lucroBruto - despesasOp; const tributosLucro = sum(l => l.natureza === 'TRIBUTO_LUCRO'); const lucroLiquido = ebitda - tributosLucro; const entradasRealizadas = sum(l => l.status === 'REALIZADO' && ['RECEITA', 'APORTE_SOCIO', 'EMPRESTIMO'].includes(l.natureza)); const saidasRealizadas = sum(l => l.status === 'REALIZADO' && ['CPV_CMV', 'DESPESA_OP', 'COMPRA_ESTOQUE', 'AQUISICAO_ATIVO', 'TRIBUTO_LUCRO', 'AMORTIZACAO'].includes(l.natureza)); const saldoCaixa = entradasRealizadas - saidasRealizadas; const contasReceber = sum(l => l.status !== 'REALIZADO' && l.natureza === 'RECEITA'); const contasPagar = sum(l => l.status === 'PREVISTO' && l.natureza !== 'RECEITA' && l.natureza !== 'TRANSFERENCIA'); const estoquesFisicoContabil = Number(valorEstoqueAtivo || 0); const ativoCirculante = saldoCaixa + contasReceber + estoquesFisicoContabil; const passivoCirculante = contasPagar; const capitalSocialAportes = sum(l => l.natureza === 'APORTE_SOCIO'); const patrimonioLiquido = capitalSocialAportes + lucroLiquido + estoquesFisicoContabil; const margemBruta = receitaLiquida > 0 ? ((lucroBruto / receitaLiquida) * 100).toFixed(1) + '%' : '—'; const liquidezCorrente = passivoCirculante > 0 ? (ativoCirculante / passivoCirculante).toFixed(2) : (ativoCirculante > 0 ? 'S/ Passivo' : '—'); const capitalGiro = ativoCirculante - passivoCirculante; return { receitaBruta, deducoesImpostos, receitaLiquida, cpv, lucroBruto, despesasOp, margemBruta, ebitda, tributosLucro, lucroLiquido, entradasRealizadas, saidasRealizadas, saldoCaixa, contasReceber, contasPagar, estoquesFisicoContabil, ativoCirculante, passivoCirculante, capitalSocialAportes, patrimonioLiquido, liquidezCorrente, capitalGiro }; } function calcularDemonstrativosFinanceiros() { return calcularDemonstrativosPorLista(db.financeiro?.lancamentos || [], calcularValorContabilTotalEstoque()); } function calcularDemonstrativosFinanceirosDefense() { return calcularDemonstrativosPorLista(db.defense?.financeiro?.lancamentos || [], calcularValorContabilTotalEstoqueDefense()); } function calcularDemonstrativosConsolidadosHolding() { const av = calcularDemonstrativosFinanceiros(); const df = calcularDemonstrativosFinanceirosDefense(); const todosLanc = [...(db.financeiro?.lancamentos || []), ...(db.defense?.financeiro?.lancamentos || [])]; const estoqueTotalHolding = av.estoquesFisicoContabil + df.estoquesFisicoContabil; const macro = calcularDemonstrativosPorLista(todosLanc, estoqueTotalHolding); return { av, df, macro }; } function montarBlocoDemonstrativoHTML(f, labelContaEstoque = '1.1.03') { const linha = (rotulo, valor, extraCls = '') => ` <div class="flex justify-between items-baseline gap-2 py-1.5 border-b border-zinc-100 min-w-0 ${extraCls}"> <span class="text-[11px] leading-snug min-w-0 flex-1 pr-1 break-words">${rotulo}</span> <b class="font-mono text-xs shrink-0 whitespace-nowrap">${valor}</b> </div> `; const linhaDestaque = (rotulo, valor, extraCls = '') => ` <div class="flex flex-wrap justify-between items-baseline gap-2 pt-2.5 mt-1 font-extrabold text-xs min-w-0 ${extraCls}"> <span class="min-w-0 flex-1 pr-1 leading-snug">${rotulo}</span> <b class="font-mono text-sm shrink-0 whitespace-nowrap">${valor}</b> </div> `; const dreHTML = ` ${linha('(+) Receita Operacional Bruta', fmtBRL(f.receitaBruta))} ${linha('(-) Deduções / Impostos s/ Venda', fmtBRL(f.deducoesImpostos), 'text-zinc-500')} ${linha('(=) Receita Operacional Líquida', fmtBRL(f.receitaLiquida), 'font-semibold')} ${linha('(-) CPV / CSP (Custos Diretos)', fmtBRL(f.cpv), 'text-rose-700')} ${linha(`(=) Lucro Bruto (Margem: ${f.margemBruta})`, fmtBRL(f.lucroBruto), 'font-semibold')} ${linha('(-) Despesas Operacionais & Comissões (4%)', fmtBRL(f.despesasOp), 'text-rose-700')} ${linha('(=) EBITDA Gerencial', fmtBRL(f.ebitda), 'font-bold text-sky-900')} ${linhaDestaque('(=) Resultado Líquido do Exercício', fmtBRL(f.lucroLiquido), f.lucroLiquido >= 0 ? 'text-emerald-700' : 'text-rose-700')} `; const dfcHTML = ` ${linha('(+) Entradas Efetivadas em Caixa', fmtBRL(f.entradasRealizadas), 'text-emerald-700')} ${linha('(-) Saídas / Pagamentos Realizados', fmtBRL(f.saidasRealizadas), 'text-rose-700')} ${linha('(=) Saldo Disponível em Caixa', fmtBRL(f.saldoCaixa), 'text-sky-800 font-semibold')} ${linha('(+) Contas a Receber (Previsto)', fmtBRL(f.contasReceber), 'text-zinc-600')} ${linha('(-) Contas a Pagar (Previsto)', fmtBRL(f.contasPagar), 'text-zinc-600')} ${linhaDestaque('(=) Caixa Projetado Líquido', fmtBRL(f.saldoCaixa + f.contasReceber - f.contasPagar), 'text-zinc-950')} `; const bpHTML = ` ${linha('Disponibilidades (Caixa e Bancos)', fmtBRL(f.saldoCaixa))} ${linha('Clientes / Contas a Receber', fmtBRL(f.contasReceber))} ${linha(`Estoques Contábeis (${labelContaEstoque})`, fmtBRL(f.estoquesFisicoContabil), 'text-amber-800 font-semibold')} <div class="flex flex-wrap justify-between items-baseline gap-2 py-1.5 px-2 border-b border-zinc-200 font-bold bg-zinc-50 rounded min-w-0"> <span class="text-[11px] min-w-0 flex-1 pr-1">TOTAL DO ATIVO CIRCULANTE</span> <b class="font-mono text-xs shrink-0 whitespace-nowrap">${fmtBRL(f.ativoCirculante)}</b> </div> ${linha('Passivo Circulante (Obrigações)', fmtBRL(f.passivoCirculante), 'text-rose-700')} ${linha('Capital Social / Aportes + Reservas', fmtBRL(f.capitalSocialAportes + f.estoquesFisicoContabil))} ${linhaDestaque('PATRIMÔNIO LÍQUIDO CONTÁBIL', fmtBRL(f.patrimonioLiquido), 'text-emerald-800')} `; return { dreHTML, dfcHTML, bpHTML }; } function aplicarEscopoVisaoFinanceiraPorPerfil() { const ehHolding = usuarioEhDiretoriaHolding(); const ehDirDrones = !ehHolding && usuarioEhDiretorDrones(); const ehDirDef = !ehHolding && usuarioEhDiretorDefense(); const barSub = document.getElementById('fin-subtabs-bar'); const btnDash = document.getElementById('subbtn-fin-dash'); const btnDre = document.getElementById('subbtn-fin-dre'); const btnDef = document.getElementById('subbtn-fin-defense_fin'); const btnPlano = document.getElementById('subbtn-fin-plano'); const btnPdfHold = document.getElementById('btn-pdf-holding-fin'); const lblBtnPdf = document.getElementById('lbl-btn-pdf-fin'); const kicker = document.getElementById('fin-header-kicker'); const subtitle = document.getElementById('fin-header-subtitle'); const titPlano = document.getElementById('fin-plano-contas-titulo'); if (btnPdfHold) btnPdfHold.classList.remove('hidden'); if (ehHolding) { if (barSub) barSub.classList.remove('hidden'); [btnDash, btnDre, btnDef, btnPlano].forEach(el => el && el.classList.remove('hidden')); if (btnDre) btnDre.innerHTML = `<i class="fa-solid fa-parachute-box"></i> 2. Fin. LES Drones <span id="badge-fin-lanc" class="subtab-badge">${db.financeiro.lancamentos.filter(l => !l.estornado).length}</span>`; if (lblBtnPdf) lblBtnPdf.textContent = 'PDF Balanço & DRE'; if (kicker) kicker.innerHTML = `<i class="fa-solid fa-building-columns mr-1"></i> Conselho & Diretoria • LES Aviation Holding`; if (subtitle) subtitle.textContent = `Balanço Consolidado da Holding • LES Drones • LES Defense`; if (titPlano) titPlano.textContent = 'Plano de Contas Contábil'; } else if (ehDirDrones) { if (barSub) barSub.classList.remove('hidden'); if (btnDash) btnDash.classList.add('hidden'); if (btnDef) btnDef.classList.add('hidden'); [btnDre, btnPlano].forEach(el => el && el.classList.remove('hidden')); if (btnDre) btnDre.innerHTML = `<i class="fa-solid fa-parachute-box"></i> 1. DRE, Balanço & Lançamentos <span id="badge-fin-lanc" class="subtab-badge">${db.financeiro.lancamentos.filter(l => !l.estornado).length}</span>`; if (btnPlano) btnPlano.innerHTML = `<i class="fa-solid fa-list-ol"></i> 2. Plano de Contas & Metas`; if (lblBtnPdf) lblBtnPdf.textContent = 'Emitir PDF Balanço & DRE (LES Drones)'; if (kicker) kicker.innerHTML = `<i class="fa-solid fa-parachute-box mr-1"></i> Diretoria Financeira • LES Drones`; if (subtitle) subtitle.textContent = `DRE Gerencial, Fluxo de Caixa, Lançamentos e Balanço Patrimonial — LES Drones`; if (titPlano) titPlano.textContent = 'Plano de Contas — LES Drones'; const dashAtiva = !document.getElementById('subtab-fin-dash')?.classList.contains('hidden'); const defAtiva = !document.getElementById('subtab-fin-defense_fin')?.classList.contains('hidden'); if (dashAtiva || defAtiva) switchSubTab('fin', 'dre'); } else if (ehDirDef) { if (barSub) barSub.classList.add('hidden'); [btnDash, btnDre, btnPlano].forEach(el => el && el.classList.add('hidden')); if (btnDef) btnDef.classList.remove('hidden'); if (lblBtnPdf) lblBtnPdf.textContent = 'Emitir PDF Balanço & DRE (LES Defense)'; if (kicker) kicker.innerHTML = `<i class="fa-solid fa-shield-halved mr-1"></i> Diretoria Financeira • LES Defense`; if (subtitle) subtitle.textContent = `DRE Gerencial, Fluxo de Caixa, Lançamentos e Balanço Patrimonial — LES Defense`; switchSubTab('fin', 'defense_fin'); } } function renderFinanceiro() { if (!usuarioTemVisaoFinanceira()) return; aplicarEscopoVisaoFinanceiraPorPerfil(); const ehHolding = usuarioEhDiretoriaHolding(); const ehDirDrones = !ehHolding && usuarioEhDiretorDrones(); const ehDirDef = !ehHolding && usuarioEhDiretorDefense(); const { av, df, macro } = calcularDemonstrativosConsolidadosHolding(); if (ehDirDrones) { ['fin-kpis-grid', 'fin-projecao-box', 'tabela-fin-comparativo-holding', 'fin-holding-dre-container', 'fin-holding-dfc-container', 'fin-holding-bp-container', 'fin-dir-defense-kpis-grid', 'fin-dir-def-dre-container', 'fin-dir-def-dfc-container', 'fin-dir-def-bp-container', 'tabela-dir-def-lancamentos'].forEach(id => { const el = document.getElementById(id); if (el) el.innerHTML = ''; }); } else if (ehDirDef) { ['fin-kpis-grid', 'fin-projecao-box', 'tabela-fin-comparativo-holding', 'fin-holding-dre-container', 'fin-holding-dfc-container', 'fin-holding-bp-container', 'fin-aviation-kpis-grid', 'fin-dre-container', 'fin-dfc-container', 'fin-bp-container', 'tabela-fin-lancamentos', 'tabela-plano-contas'].forEach(id => { const el = document.getElementById(id); if (el) el.innerHTML = ''; }); } const elMacroKpis = document.getElementById('fin-kpis-grid'); if (elMacroKpis && ehHolding) { const subBreakdown = (vAv, vDef) => ` <div class="mt-2 pt-1.5 border-t border-zinc-100 text-[10px] text-zinc-500 space-y-0.5"> <div class="flex justify-between items-center gap-1"><span>Aviation:</span><b class="font-mono text-zinc-700 whitespace-nowrap">${fmtBRL(vAv)}</b></div> <div class="flex justify-between items-center gap-1"><span>Defense:</span><b class="font-mono text-emerald-700 whitespace-nowrap">${fmtBRL(vDef)}</b></div> </div> `; elMacroKpis.innerHTML = ` <div class="les-card p-3.5 border-l-4 border-l-emerald-600 min-w-0"> <p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Caixa Consolidado (Holding)</p> <p class="text-base xl:text-lg font-extrabold text-emerald-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(macro.saldoCaixa)}</p> ${subBreakdown(av.saldoCaixa, df.saldoCaixa)} </div> <div class="les-card p-3.5 border-l-4 border-l-sky-600 min-w-0"> <p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Contas a Receber (Grupo)</p> <p class="text-base xl:text-lg font-extrabold text-sky-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(macro.contasReceber)}</p> ${subBreakdown(av.contasReceber, df.contasReceber)} </div> <div class="les-card p-3.5 border-l-4 border-l-amber-500 min-w-0"> <p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Estoques Somados (Ativo)</p> <p class="text-base xl:text-lg font-extrabold text-amber-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(macro.estoquesFisicoContabil)}</p> ${subBreakdown(av.estoquesFisicoContabil, df.estoquesFisicoContabil)} </div> <div class="les-card p-3.5 border-l-4 border-l-rose-600 min-w-0"> <p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Passivo / Contas a Pagar</p> <p class="text-base xl:text-lg font-extrabold text-rose-600 mt-1 font-mono tracking-tight break-words">${fmtBRL(macro.contasPagar)}</p> ${subBreakdown(av.contasPagar, df.contasPagar)} </div> <div class="les-card p-3.5 border-l-4 border-l-zinc-950 min-w-0"> <p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Patrimônio Líquido Holding</p> <p class="text-base xl:text-lg font-extrabold text-zinc-950 mt-1 font-mono tracking-tight break-words">${fmtBRL(macro.patrimonioLiquido)}</p> <div class="mt-2 pt-1.5 border-t border-zinc-100 text-[10px] text-zinc-500 space-y-0.5"> <div class="flex justify-between items-center gap-1"><span>Lucro Líquido:</span><b class="font-mono text-zinc-800 whitespace-nowrap">${fmtBRL(macro.lucroLiquido)}</b></div> <div class="flex justify-between items-center gap-1"><span>EBITDA:</span><b class="font-mono text-sky-700 whitespace-nowrap">${fmtBRL(macro.ebitda)}</b></div> </div> </div> `; } const elProjBox = document.getElementById('fin-projecao-box'); if (elProjBox && ehHolding) { elProjBox.innerHTML = ` <div class="bg-zinc-50 p-3.5 rounded-xl border border-zinc-200 min-w-0 flex flex-col justify-between"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">Liquidez Corrente (Holding)</span> <b class="text-sm font-extrabold text-zinc-950 font-mono mt-1 block break-words">${macro.liquidezCorrente}</b> </div> <div class="bg-zinc-50 p-3.5 rounded-xl border border-zinc-200 min-w-0 flex flex-col justify-between"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">Capital de Giro Líquido</span> <b class="text-sm font-extrabold text-emerald-700 font-mono mt-1 block break-words">${fmtBRL(macro.capitalGiro)}</b> </div> <div class="bg-zinc-50 p-3.5 rounded-xl border border-zinc-200 min-w-0 flex flex-col justify-between"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">Ativo Circulante Total</span> <b class="text-sm font-extrabold text-sky-800 font-mono mt-1 block break-words">${fmtBRL(macro.ativoCirculante)}</b> </div> <div class="bg-zinc-50 p-3.5 rounded-xl border border-zinc-200 min-w-0 flex flex-col justify-between"> <span class="text-[10px] font-bold uppercase text-zinc-500 block">EBITDA Consolidado</span> <b class="text-sm font-extrabold text-zinc-950 font-mono mt-1 block break-words">${fmtBRL(macro.ebitda)}</b> </div> `; } const tbodyComp = document.getElementById('tabela-fin-comparativo-holding'); if (tbodyComp && ehHolding) { const linhasComp = [ { nome: '1. Receita Operacional Líquida', a: av.receitaLiquida, d: df.receitaLiquida, m: macro.receitaLiquida, bold: false }, { nome: '2. Custos Diretos (CPV / CSP)', a: av.cpv, d: df.cpv, m: macro.cpv, bold: false }, { nome: '3. Despesas Operacionais & Comissões (4%)', a: av.despesasOp, d: df.despesasOp, m: macro.despesasOp, bold: false }, { nome: '4. EBITDA Gerencial', a: av.ebitda, d: df.ebitda, m: macro.ebitda, bold: true }, { nome: '5. Resultado / Lucro Líquido do Período', a: av.lucroLiquido, d: df.lucroLiquido, m: macro.lucroLiquido, bold: true }, { nome: '6. Disponibilidades em Caixa (Realizado)', a: av.saldoCaixa, d: df.saldoCaixa, m: macro.saldoCaixa, bold: false }, { nome: '7. Contas a Receber de Clientes', a: av.contasReceber, d: df.contasReceber, m: macro.contasReceber, bold: false }, { nome: '8. Valor Contábil em Estoque (Ativo 1.1.03)', a: av.estoquesFisicoContabil, d: df.estoquesFisicoContabil, m: macro.estoquesFisicoContabil, bold: true }, { nome: '9. Total do Ativo Circulante', a: av.ativoCirculante, d: df.ativoCirculante, m: macro.ativoCirculante, bold: true }, { nome: '10. Passivo Circulante (Obrigações a Pagar)', a: av.passivoCirculante, d: df.passivoCirculante, m: macro.passivoCirculante, bold: false }, { nome: '11. Patrimônio Líquido Contábil', a: av.patrimonioLiquido, d: df.patrimonioLiquido, m: macro.patrimonioLiquido, bold: true } ]; tbodyComp.innerHTML = linhasComp.map(r => ` <tr class="${r.bold ? 'bg-zinc-50 font-bold text-zinc-950' : 'hover:bg-zinc-50'}"> <td class="p-2.5">${r.nome}</td> <td class="p-2.5 text-right font-mono">${fmtBRL(r.a)}</td> <td class="p-2.5 text-right font-mono text-emerald-800">${fmtBRL(r.d)}</td> <td class="p-2.5 text-right font-mono font-extrabold bg-zinc-100">${fmtBRL(r.m)}</td> </tr> `).join(''); } const blocosMacro = montarBlocoDemonstrativoHTML(macro, 'Aviation + Defense'); const elHoldDre = document.getElementById('fin-holding-dre-container'); const elHoldDfc = document.getElementById('fin-holding-dfc-container'); const elHoldBp = document.getElementById('fin-holding-bp-container'); if (elHoldDre) elHoldDre.innerHTML = blocosMacro.dreHTML; if (elHoldDfc) elHoldDfc.innerHTML = blocosMacro.dfcHTML; if (elHoldBp) elHoldBp.innerHTML = blocosMacro.bpHTML; const elAvKpis = document.getElementById('fin-aviation-kpis-grid'); if (elAvKpis && (ehHolding || ehDirDrones)) { elAvKpis.innerHTML = ` <div class="les-card p-3.5 border-l-4 border-l-emerald-600 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Caixa LES Aviation</p><p class="text-base xl:text-lg font-extrabold text-emerald-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(av.saldoCaixa)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-sky-600 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">A Receber (Drones/Paraq.)</p><p class="text-base xl:text-lg font-extrabold text-sky-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(av.contasReceber)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-amber-500 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Estoque Aeronáutico (1.1.03)</p><p class="text-base xl:text-lg font-extrabold text-amber-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(av.estoquesFisicoContabil)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-rose-600 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">A Pagar (P.O. / Comissões 4%)</p><p class="text-base xl:text-lg font-extrabold text-rose-600 mt-1 font-mono tracking-tight break-words">${fmtBRL(av.contasPagar)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-zinc-900 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Patrimônio Líquido Aviation</p><p class="text-base xl:text-lg font-extrabold text-zinc-900 mt-1 font-mono tracking-tight break-words">${fmtBRL(av.patrimonioLiquido)}</p></div> `; } const blocosAv = montarBlocoDemonstrativoHTML(av, 'Conta 1.1.03 - Aeronáutico'); const elAvDre = document.getElementById('fin-dre-container'); const elAvDfc = document.getElementById('fin-dfc-container'); const elAvBp = document.getElementById('fin-bp-container'); if (elAvDre) elAvDre.innerHTML = blocosAv.dreHTML; if (elAvDfc) elAvDfc.innerHTML = blocosAv.dfcHTML; if (elAvBp) elAvBp.innerHTML = blocosAv.bpHTML; const elDirDefKpis = document.getElementById('fin-dir-defense-kpis-grid'); if (elDirDefKpis && (ehHolding || ehDirDef)) { elDirDefKpis.innerHTML = ` <div class="les-card p-3.5 border-l-4 border-l-emerald-600 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Caixa LES Defense</p><p class="text-base xl:text-lg font-extrabold text-emerald-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(df.saldoCaixa)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-sky-600 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">A Receber (Contratos Defense)</p><p class="text-base xl:text-lg font-extrabold text-sky-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(df.contasReceber)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-amber-500 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Estoque Defense (1.1.03-DEF)</p><p class="text-base xl:text-lg font-extrabold text-amber-700 mt-1 font-mono tracking-tight break-words">${fmtBRL(df.estoquesFisicoContabil)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-rose-600 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Obrigações Defense a Pagar</p><p class="text-base xl:text-lg font-extrabold text-rose-600 mt-1 font-mono tracking-tight break-words">${fmtBRL(df.contasPagar)}</p></div> <div class="les-card p-3.5 border-l-4 border-l-zinc-950 min-w-0"><p class="text-[10px] font-bold uppercase text-zinc-500 truncate">Patrimônio Líquido Defense</p><p class="text-base xl:text-lg font-extrabold text-zinc-950 mt-1 font-mono tracking-tight break-words">${fmtBRL(df.patrimonioLiquido)}</p></div> `; } const blocosDef = montarBlocoDemonstrativoHTML(df, 'Conta 1.1.03-DEF - Sensores'); const elDirDefDre = document.getElementById('fin-dir-def-dre-container'); const elDirDefDfc = document.getElementById('fin-dir-def-dfc-container'); const elDirDefBp = document.getElementById('fin-dir-def-bp-container'); if (elDirDefDre) elDirDefDre.innerHTML = blocosDef.dreHTML; if (elDirDefDfc) elDirDefDfc.innerHTML = blocosDef.dfcHTML; if (elDirDefBp) elDirDefBp.innerHTML = blocosDef.bpHTML; const tbodyDirDefLanc = document.getElementById('tabela-dir-def-lancamentos'); if (tbodyDirDefLanc && (ehHolding || ehDirDef)) { tbodyDirDefLanc.innerHTML = (db.defense?.financeiro?.lancamentos || []).map(l => ` <tr class="${l.estornado ? 'opacity-40 line-through bg-rose-50' : ''}"> <td class="p-3 font-mono font-bold">${l.id}<span class="block text-[10px] font-normal text-zinc-400">${l.doc || ''}</span></td> <td class="p-3 font-mono">${fmtDataBR(l.competencia)}<span class="block text-[10px] text-zinc-400">Venc: ${fmtDataBR(l.vencimento)}</span></td> <td class="p-3 font-semibold">${l.natureza}<span class="block text-[10px] font-normal text-zinc-500">${l.categoria}</span></td> <td class="p-3">${l.desc} <span class="text-zinc-400">(${l.entidade || ''})</span>${etiquetaRastreabilidade(l)}</td> <td class="p-3 font-bold">${fmtBRL(l.liquido)}</td> <td class="p-3"><span class="px-2 py-0.5 rounded text-[10px] font-bold ${l.status === 'REALIZADO' || l.status === 'CONTABIL_ESTOQUE' ? 'bg-emerald-100 text-emerald-800' : l.status === 'ESTORNADO' ? 'bg-rose-100 text-rose-800' : 'bg-amber-100 text-amber-800'}">${l.status}</span></td> <td class="p-3 text-right"> <button onclick="conciliarLancamentoDefense('${l.id}')" class="px-2 py-1 bg-zinc-900 text-white rounded text-[10px]">Conciliar</button> <button onclick="estornarLancamentoDefense('${l.id}')" class="px-2 py-1 bg-rose-100 text-rose-700 rounded text-[10px]">Estornar</button> </td> </tr> `).join(''); } const elTabFinLanc = document.getElementById('tabela-fin-lancamentos'); if (elTabFinLanc && (ehHolding || ehDirDrones)) { elTabFinLanc.innerHTML = db.financeiro.lancamentos.map(l => ` <tr class="${l.estornado ? 'opacity-40 line-through bg-rose-50' : ''}"> <td class="p-3 font-mono font-bold">${l.id}<span class="block text-[10px] font-normal text-zinc-400">${l.doc || ''}</span></td> <td class="p-3 font-mono">${l.competencia}<span class="block text-[10px] text-zinc-400">Venc: ${l.vencimento}</span></td> <td class="p-3 font-semibold">${l.natureza}<span class="block text-[10px] font-normal text-zinc-500">${l.categoria}</span></td> <td class="p-3">${l.desc} <span class="text-zinc-400">(${l.entidade || ''})</span>${etiquetaRastreabilidade(l)}</td> <td class="p-3 font-bold">${fmtBRL(l.liquido)}</td> <td class="p-3"><span class="px-2 py-0.5 rounded text-[10px] font-bold ${l.status === 'REALIZADO' || l.status === 'CONTABIL_ESTOQUE' ? 'bg-emerald-100 text-emerald-800' : l.status === 'ESTORNADO' ? 'bg-rose-100 text-rose-800' : 'bg-amber-100 text-amber-800'}">${l.status}</span></td> <td class="p-3 text-right"><button onclick="conciliarLancamento('${l.id}')" class="px-2 py-1 bg-zinc-900 text-white rounded text-[10px]">Conciliar</button> <button onclick="estornarLancamento('${l.id}')" class="px-2 py-1 bg-rose-100 text-rose-700 rounded text-[10px]">Estornar</button></td> </tr> `).join(''); } const elTabPlano = document.getElementById('tabela-plano-contas'); if (elTabPlano) { elTabPlano.innerHTML = db.financeiro.planoContas.map(c => `<tr><td class="p-2 font-mono font-bold">${c.codigo}</td><td class="p-2">${c.nome}</td><td class="p-2 text-[10px]">${c.grupo}</td></tr>`).join(''); } } function gerarPDFRelatorioHoldingConsolidado() { if (!exigirPermissao(['diretoria', 'diretor_drones', 'diretor_defense'], 'Emitir Relatório PDF de Balanço & DRE')) return; const ehHolding = usuarioEhDiretoriaHolding(); const ehDirDef = !ehHolding && usuarioEhDiretorDefense(); const ehDirDrones = !ehHolding && usuarioEhDiretorDrones(); const { av, df, macro } = calcularDemonstrativosConsolidadosHolding(); const agoraStr = carimboHoraAtual(); if (ehDirDef || ehDirDrones) { const dadosSub = ehDirDef ? df : av; const nomeUnidade = ehDirDef ? 'LES DEFENSE' : 'LES DRONES'; const subtituloLogo = ehDirDef ? 'LES DEFENSE • DEMONSTRATIVO CONTÁBIL E FINANCEIRO' : 'LES DRONES • DEMONSTRATIVO CONTÁBIL E FINANCEIRO'; const contaEst = ehDirDef ? '1.1.03-DEF' : '1.1.03'; const lancamentosUnidade = (ehDirDef ? (db.defense?.financeiro?.lancamentos || []) : (db.financeiro?.lancamentos || [])).filter(l => !l.estornado); const linhasUnidade = [ ['1. Receita Operacional Bruta', dadosSub.receitaBruta, false], ['2. (-) Deduções e Impostos sobre Vendas', dadosSub.deducoesImpostos, false], ['3. (=) Receita Operacional Líquida', dadosSub.receitaLiquida, true], ['4. (-) Custos Diretos (CPV / CSP)', dadosSub.cpv, false], ['5. (=) Lucro Bruto (Margem: ' + dadosSub.margemBruta + ')', dadosSub.lucroBruto, true], ['6. (-) Despesas Operacionais & Comissões (4%)', dadosSub.despesasOp, false], ['7. (=) EBITDA Gerencial', dadosSub.ebitda, true], ['8. (=) Resultado Líquido do Exercício', dadosSub.lucroLiquido, true], ['9. Disponibilidades em Caixa e Bancos (Realizado)', dadosSub.saldoCaixa, false], ['10. Contas a Receber de Clientes', dadosSub.contasReceber, false], ['11. Estoques Contábeis (' + contaEst + ')', dadosSub.estoquesFisicoContabil, true], ['12. TOTAL DO ATIVO CIRCULANTE', dadosSub.ativoCirculante, true], ['13. Passivo Circulante (Obrigações a Pagar)', dadosSub.passivoCirculante, false], ['14. Capital Social / Aportes + Reservas', dadosSub.capitalSocialAportes + dadosSub.estoquesFisicoContabil, false], ['15. PATRIMÔNIO LÍQUIDO CONTÁBIL', dadosSub.patrimonioLiquido, true] ].map(([label, val, isBold]) => ` <tr style="${isBold ? 'background:#f4f4f5; font-weight:800;' : ''}"> <td style="padding:8px 12px; border-bottom:1px solid #d4d4d8;">${label}</td> <td style="padding:8px 12px; border-bottom:1px solid #d4d4d8; text-align:right; font-family:monospace; font-weight:${isBold ? '900' : '600'};">${fmtBRL(val)}</td> </tr> `).join(''); const linhasExtrato = lancamentosUnidade.slice(0, 25).map(l => ` <tr> <td style="padding:6px 8px; border-bottom:1px solid #e4e4e7; font-family:monospace;">${l.id}</td> <td style="padding:6px 8px; border-bottom:1px solid #e4e4e7;">${fmtDataBR(l.competencia)}</td> <td style="padding:6px 8px; border-bottom:1px solid #e4e4e7;"><b>${l.natureza}</b><br><span style="font-size:9.5px; color:#52525b;">${l.categoria}</span></td> <td style="padding:6px 8px; border-bottom:1px solid #e4e4e7;">${l.desc} (${l.entidade || '-'})</td> <td style="padding:6px 8px; border-bottom:1px solid #e4e4e7; text-align:center;">${l.status}</td> <td style="padding:6px 8px; border-bottom:1px solid #e4e4e7; text-align:right; font-family:monospace; font-weight:700;">${fmtBRL(l.liquido)}</td> </tr> `).join(''); const winSub = window.open('', '_blank'); winSub.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Balanço Patrimonial & DRE — ${nomeUnidade}</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 15mm 16mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } table { width: 100%; border-collapse: collapse; font-size: 11px; } th { background: #09090b; color: #ffffff; text-transform: uppercase; font-size: 9.5px; padding: 8px 10px; text-align: left; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>${nomeUnidade}</b> • Balanço Patrimonial, DRE Gerencial & Demonstrativo Financeiro</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir Balanço</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:16px;"> ${obterLogoHTMLDocumento(subtituloLogo)} <div style="text-align:right;"> <div style="font-size:15px; font-weight:900; text-transform:uppercase;">BALANÇO PATRIMONIAL, DRE & FLUXO DE CAIXA — ${nomeUnidade}</div> <div style="font-size:11px; color:#3f3f46; margin-top:4px;">EMISSÃO: <b>${agoraStr}</b> | DIRETORIA: <b>${sessaoAtual.nome || ''}</b></div> </div> </div> <div style="font-size:10px; font-weight:800; text-transform:uppercase; margin-bottom:6px; letter-spacing:0.08em;">01. DEMONSTRATIVO CONTÁBIL, DRE E POSIÇÃO PATRIMONIAL (${nomeUnidade})</div> <table style="border:1px solid #d4d4d8; margin-bottom:18px;"> <thead> <tr> <th>CONTA / INDICADOR GERENCIAL E PATRIMONIAL</th> <th style="text-align:right;">SALDO / VALOR (${nomeUnidade})</th> </tr> </thead> <tbody>${linhasUnidade}</tbody> </table> <div style="font-size:10px; font-weight:800; text-transform:uppercase; margin-bottom:6px; letter-spacing:0.08em;">02. EXTRATO DE LANÇAMENTOS FINANCEIROS (${nomeUnidade})</div> <table style="border:1px solid #d4d4d8;"> <thead> <tr> <th>ID</th> <th>COMPETÊNCIA</th> <th>NATUREZA / CATEGORIA</th> <th>DESCRIÇÃO & ENTIDADE</th> <th style="text-align:center;">STATUS</th> <th style="text-align:right;">VALOR LÍQUIDO</th> </tr> </thead> <tbody>${linhasExtrato || '<tr><td colspan="6" style="padding:10px; text-align:center; color:#71717a;">Nenhum lançamento registrado no período.</td></tr>'}</tbody> </table> </div> <div style="margin-top:24px;"> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>DIRETORIA EXECUTIVA — ${nomeUnidade}</b><br>${sessaoAtual.nome || ''} </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>CONCILIAÇÃO CONTÁBIL & FINANCEIRA</b><br>Demonstrativo Oficial Emitido pelo Sistema </div> </div> </div> </div> </body> </html> `); winSub.document.close(); return; } const linhasTabela = [ ['1. Receita Operacional Bruta', av.receitaBruta, df.receitaBruta, macro.receitaBruta, false], ['2. Receita Operacional Líquida', av.receitaLiquida, df.receitaLiquida, macro.receitaLiquida, true], ['3. Custos Diretos (CPV / CSP)', av.cpv, df.cpv, macro.cpv, false], ['4. Lucro Bruto', av.lucroBruto, df.lucroBruto, macro.lucroBruto, true], ['5. Despesas Operacionais & Comissões (4%)', av.despesasOp, df.despesasOp, macro.despesasOp, false], ['6. EBITDA Gerencial', av.ebitda, df.ebitda, macro.ebitda, true], ['7. Resultado Líquido do Exercício', av.lucroLiquido, df.lucroLiquido, macro.lucroLiquido, true], ['8. Disponibilidades em Caixa (Realizado)', av.saldoCaixa, df.saldoCaixa, macro.saldoCaixa, false], ['9. Contas a Receber de Clientes', av.contasReceber, df.contasReceber, macro.contasReceber, false], ['10. Estoques Contábeis (1.1.03 / 1.1.03-DEF)', av.estoquesFisicoContabil, df.estoquesFisicoContabil, macro.estoquesFisicoContabil, true], ['11. Total do Ativo Circulante', av.ativoCirculante, df.ativoCirculante, macro.ativoCirculante, true], ['12. Passivo Circulante (Obrigações a Pagar)', av.passivoCirculante, df.passivoCirculante, macro.passivoCirculante, false], ['13. Capital Social / Aportes + Reservas', av.capitalSocialAportes + av.estoquesFisicoContabil, df.capitalSocialAportes + df.estoquesFisicoContabil, macro.capitalSocialAportes + macro.estoquesFisicoContabil, false], ['14. PATRIMÔNIO LÍQUIDO CONTÁBIL', av.patrimonioLiquido, df.patrimonioLiquido, macro.patrimonioLiquido, true] ].map(([label, vAv, vDf, vMac, isBold]) => ` <tr style="${isBold ? 'background:#f4f4f5; font-weight:800;' : ''}"> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8;">${label}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right; font-family:monospace;">${fmtBRL(vAv)}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right; font-family:monospace; color:#047857;">${fmtBRL(vDf)}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right; font-family:monospace; font-weight:900; background:#e4e4e7;">${fmtBRL(vMac)}</td> </tr> `).join(''); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Balanço Consolidado Holding LES Aviation & Subsidiárias</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 15mm 16mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } table { width: 100%; border-collapse: collapse; font-size: 11px; } th { background: #09090b; color: #ffffff; text-transform: uppercase; font-size: 9.5px; padding: 9px 10px; text-align: left; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>HOLDING LES AVIATION</b> • Demonstrativo Consolidado do Conselho</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir Balanço</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:16px;"> ${obterLogoHTMLDocumento('CONSELHO & HOLDING LES AVIATION • ALOJA BRASIL COMERCIAL LTDA')} <div style="text-align:right;"> <div style="font-size:15px; font-weight:900; text-transform:uppercase;">BALANÇO PATRIMONIAL & DRE CONSOLIDADO — HOLDING LES</div> <div style="font-size:11px; color:#3f3f46; margin-top:4px;">EMISSÃO: <b>${agoraStr}</b> | CONSELHO / DIRETORIA: <b>${sessaoAtual.nome || 'Diretoria'}</b></div> </div> </div> <table style="border:1px solid #d4d4d8;"> <thead> <tr> <th>CONTA / INDICADOR GERENCIAL E PATRIMONIAL</th> <th style="text-align:right;">LES DRONES</th> <th style="text-align:right;">LES DEFENSE</th> <th style="text-align:right;">CONSOLIDADO HOLDING LES</th> </tr> </thead> <tbody>${linhasTabela}</tbody> </table> </div> <div style="margin-top:24px;"> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>CEO & CONSELHO — HOLDING LES AVIATION</b><br>Consolidação Corporativa </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>CONTROLADORIA & AUDITORIA CONTÁBIL</b><br>Demonstrativo Consolidado do Grupo </div> </div> </div> </div> </body> </html> `); win.document.close(); } function renderAuditoria() { const tbody = document.getElementById('dir-auditoria-tbody'); if (!tbody) return; const termo = normalizarTexto(document.getElementById('filtro-auditoria')?.value || ''); const lista = db.auditoria.filter(a => !termo || normalizarTexto(a.usuario).includes(termo) || normalizarTexto(a.acao).includes(termo)); tbody.innerHTML = lista.slice(0, 100).map(a => `<tr><td class="p-2 font-mono text-[11px]">${a.dataHora}</td><td class="p-2 font-bold">${a.usuario}</td><td class="p-2">${a.modulo}</td><td class="p-2">${a.acao}</td><td class="p-2 font-mono">${a.refId}</td></tr>`).join(''); } // ========================================================= // RENDERIZADOR DA VISÃO DE COMISSÕES (4%) DO VENDEDOR // ========================================================= function renderComissoesVendedor() { const bannerEl = document.getElementById('ven-comissoes-kpis-banner'); if (!bannerEl) return; if (!sessaoAtual.nome) { bannerEl.classList.add('hidden'); return; } const minhasPropostasAtivas = (db.propostas || []).filter(p => p.status !== 'RECUSADA' && p.status !== 'CANCELADA' && usuarioEhResponsavelPelaVenda(p.vendedor || p.criadoPor) ); if (minhasPropostasAtivas.length === 0) { bannerEl.classList.add('hidden'); bannerEl.innerHTML = ''; return; } const totalBasePropostas = minhasPropostasAtivas.reduce((acc, p) => acc + Number(p.total || 0), 0); const valorPrevistoEmAnalise = minhasPropostasAtivas .filter(p => p.status !== 'APROVADA' && p.status !== 'ACEITA') .reduce((acc, p) => acc + Number(p.comissaoValor ?? (Number(p.total || 0) * TAXA_COMISSAO_VENDEDOR)), 0); const valorAprovadoGarantido = minhasPropostasAtivas .filter(p => p.status === 'APROVADA' || p.status === 'ACEITA') .reduce((acc, p) => acc + Number(p.comissaoValor ?? (Number(p.total || 0) * TAXA_COMISSAO_VENDEDOR)), 0); const valorJaPago = minhasPropostasAtivas .filter(p => p.comissaoStatus === 'PAGA') .reduce((acc, p) => acc + Number(p.comissaoValor ?? (Number(p.total || 0) * TAXA_COMISSAO_VENDEDOR)), 0); bannerEl.classList.remove('hidden'); bannerEl.innerHTML = ` <div class="les-card p-4 border-l-4 border-l-zinc-950"> <p class="text-[10px] font-bold uppercase text-zinc-500">Minhas Vendas / Propostas (${sessaoAtual.nome})</p> <p class="text-xl font-extrabold text-zinc-950 mt-1 font-mono">${fmtBRL(totalBasePropostas)}</p> <p class="text-[11px] text-zinc-400">${minhasPropostasAtivas.length} proposta(s) sob minha responsabilidade</p> </div> <div class="les-card p-4 border-l-4 border-l-amber-500"> <p class="text-[10px] font-bold uppercase text-amber-700">Meu Retorno Previsto (Em Análise)</p> <p class="text-xl font-extrabold text-amber-700 mt-1 font-mono">${fmtBRL(valorPrevistoEmAnalise)}</p> <p class="text-[11px] text-zinc-400">Propostas pendentes de aprovação</p> </div> <div class="les-card p-4 border-l-4 border-l-emerald-600"> <p class="text-[10px] font-bold uppercase text-emerald-700">Meu Retorno Aprovado (Pedidos Ativos)</p> <p class="text-xl font-extrabold text-emerald-700 mt-1 font-mono">${fmtBRL(valorAprovadoGarantido)}</p> <p class="text-[11px] text-zinc-400">Pedidos aprovados / P.O. emitido</p> </div> <div class="les-card p-4 border-l-4 border-l-sky-600"> <p class="text-[10px] font-bold uppercase text-sky-700">Total Liquidado (${sessaoAtual.nome})</p> <p class="text-xl font-extrabold text-sky-700 mt-1 font-mono">${fmtBRL(valorJaPago)}</p> <p class="text-[11px] text-zinc-400">Acumulado: ${fmtBRL(valorPrevistoEmAnalise + valorAprovadoGarantido)}</p> </div> `; } function atualizarBadgesAbas() { const setBadge = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; }; setBadge('badge-ven-prop', db.propostas.length); setBadge('badge-ven-cli', db.clientes.length); setBadge('badge-ven-vendas', db.vendas.length); setBadge('badge-log-trk', db.pos.filter(p => p.status !== 'Cancelado').length); setBadge('badge-log-pos', db.pos.length); setBadge('badge-est-itens', (db.estoque || []).length); setBadge('badge-est-mov', (db.movimentacoesEstoque || []).length); setBadge('badge-pos-poscli', db.pos.filter(p => p.status !== 'Cancelado').length); setBadge('badge-pos-dem', (db.demandasPosVenda || []).length); setBadge('badge-pos-mro', (db.ordensServicoMRO || []).length); setBadge('badge-pos-frota', db.sns.length); setBadge('badge-fin-lanc', db.financeiro.lancamentos.filter(l => !l.estornado).length); setBadge('badge-fin-def-sub', (db.defense?.financeiro?.lancamentos || []).filter(l => !l.estornado).length); setBadge('badge-def-cons', (db.defense?.consultorias || []).length); setBadge('badge-def-cont', (db.defense?.contratos || []).length); setBadge('badge-def-cli', (db.defense?.clientes || []).length); setBadge('badge-def-est', (db.defense?.estoque || []).length); if (sessaoAtual.perfil) montarMenuLateralRBAC(); renderCorreioEAlertas(); } function renderAll() { garantirEstruturaDB(); atualizarBadgesAbas(); const cliOpts = db.clientes.length > 0 ? db.clientes.map(c => `<option value="${c.id}">${c.nome} (${c.cnpj})</option>`).join('') : `<option value="">-- Cadastre um cliente na aba Clientes --</option>`; ['op-cliente', 'anam-cliente', 'prop-cliente', 'po-cliente', 'sn-cliente', 'dem-cliente', 'mro-cliente'].forEach(id => { const el = document.getElementById(id); if (el) { const valAntigo = el.value; el.innerHTML = cliOpts; if (valAntigo && db.clientes.some(c => String(c.id) === String(valAntigo))) el.value = valAntigo; } }); const defCliOpts = (db.defense?.clientes || []).length > 0 ? (db.defense?.clientes || []).map(c => `<option value="${c.id}">${c.nome} (${c.cnpj})</option>`).join('') : `<option value="">-- Cadastre um cliente em Clientes Defense --</option>`; ['def-cons-cliente', 'def-cont-cliente'].forEach(id => { const el = document.getElementById(id); if (el) { const valAntigo = el.value; el.innerHTML = defCliOpts; if (valAntigo && (db.defense?.clientes || []).some(c => String(c.id) === String(valAntigo))) { el.value = valAntigo; } } }); preencherEnderecoClienteNoPO(); const pnOpts = db.pns.map(p => `<option value="${p.pn}">${p.pn} — ${p.desc}</option>`).join(''); ['sn-pn', 'man-pn', 'est-pn'].forEach(id => { const el = document.getElementById(id); if (el) { const valAnt = el.value; el.innerHTML = pnOpts; if (valAnt) el.value = valAnt; } }); if (!document.getElementById('est-desc')?.value) aoSelecionarPNEstoque(); const snOpts = db.sns.length > 0 ? db.sns.map(s => `<option value="${s.sn}">${s.sn} (${s.snAnv})</option>`).join('') : `<option value="">-- Nenhum S.N. cadastrado na Frota --</option>`; ['log-sn', 'int-sn', 'dem-sn'].forEach(id => { const el = document.getElementById(id); if (el) { const vAnt = el.value; el.innerHTML = snOpts; if (vAnt) el.value = vAnt; } }); const estOptsDemanda = `<option value="">-- Nenhuma peça de estoque selecionada --</option>` + (db.estoque || []).map(i => `<option value="${i.id}">${i.pn} — ${i.desc} (Saldo: ${i.qtd} un.)</option>`).join(''); const elDemEst = document.getElementById('dem-estoque-item'); if (elDemEst) { const vAnt = elDemEst.value; elDemEst.innerHTML = estOptsDemanda; if (vAnt) elDemEst.value = vAnt; } document.querySelectorAll('.prop-item-row .item-pn').forEach(sel => { const valAtual = sel.value; sel.innerHTML = obterOpcoesSelectPN(valAtual); }); if (document.querySelectorAll('.prop-item-row').length === 0) { adicionarLinhaItemProposta(); } renderComissoesVendedor(); // 1. TABELA DE PROPOSTAS COMERCIAIS (MOSTRANDO APENAS O MODO DE PAGAMENTO SELECIONADO, TAXA IMP. EM %, E VALOR DO RESPONSÁVEL APENAS P/ O RESPONSÁVEL PELA VENDA) const tbodyProp = document.getElementById('tabela-propostas'); if (tbodyProp) { tbodyProp.innerHTML = db.propostas.length === 0 ? '<tr><td colspan="7" class="p-4 text-center text-zinc-400 italic">Nenhuma cotação/proposta emitida ainda.</td></tr>' : db.propostas.map(p => { const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); const aprovada = p.status === 'APROVADA' || p.status === 'ACEITA'; const inativa = p.status === 'RECUSADA' || p.status === 'CANCELADA'; const poVinc = db.pos.find(po => po.numero === p.poVinculado || String(po.refVenda) === String(p.numero)); const dossie = poVinc?.anexosPO || p.dossieVendas || {}; const okFab = !!dossie.cotacaoDRS; const okPag = !!dossie.comprovantePagto; const vendedorNome = p.vendedor || p.criadoPor || 'Lucas Azevedo'; const comissao4 = Number(p.comissaoValor ?? (Number(p.total || 0) * TAXA_COMISSAO_VENDEDOR)); const mostrarParaResponsavel = usuarioEhResponsavelPelaVenda(vendedorNome); const subCalc = Number(p.subtotal || 0); const percImp = p.taxaImpPerc !== undefined ? Number(p.taxaImpPerc) : (subCalc > 0 && Number(p.taxasImp || 0) > 0 ? Number(((Number(p.taxasImp) / subCalc) * 100).toFixed(2)) : 0); return ` <tr class="hover:bg-zinc-50 ${inativa ? 'bg-rose-50/40' : ''}"> <td class="p-3 font-mono font-bold text-zinc-950">Nº ${p.numero}<span class="block text-[10px] font-normal text-zinc-400">${fmtDataBR(p.emissao)} • Val: ${p.validade || '15d'}</span></td> <td class="p-3 font-semibold"> ${cli ? cli.nome : '-'} <span class="block text-[10px] font-bold text-emerald-800 mt-0.5"><i class="fa-solid fa-user-tie mr-1 text-emerald-600"></i>Resp.: ${vendedorNome}</span> <span class="block text-[10px] font-normal text-zinc-400">${cli ? cli.endereco : ''}</span> </td> <td class="p-3">${p.produto}</td> <td class="p-3 font-bold"> ${fmtBRL(p.total)} ${p.formaPagamento === 'PIX' ? `<span class="block text-[10px] font-bold text-emerald-700"><i class="fa-brands fa-pix mr-1"></i>Modo PIX (-5% incluso)</span>` : p.formaPagamento === 'CARTAO' ? `<span class="block text-[10px] font-bold text-sky-700"><i class="fa-regular fa-credit-card mr-1"></i>Cartão: ${p.parcelasCartao || 4}x de ${fmtBRL((p.total || 0) / (p.parcelasCartao || 4))}${(p.parcelasCartao || 4) <= 4 ? ' s/ juros' : ''}</span>` : `<span class="block text-[10px] font-bold text-zinc-700"><i class="fa-solid fa-money-bill-wave mr-1"></i>Dinheiro à Vista</span>`} ${mostrarParaResponsavel ? ` <span class="inline-block mt-1 px-2 py-0.5 rounded bg-emerald-50 border border-emerald-300 text-emerald-900 font-mono text-[10px] font-extrabold"> <i class="fa-solid fa-user-check mr-1 text-emerald-600"></i>${vendedorNome}: ${fmtBRL(comissao4)} </span> ` : ''} <span class="block text-[10px] font-normal text-zinc-400 mt-0.5">Frete: ${fmtBRL(p.frete || 0)} | Imp: ${percImp}% (${fmtBRL(p.taxasImp || 0)})</span> </td> <td class="p-3"> <span class="inline-block px-2 py-0.5 rounded text-[10px] font-bold mr-1 mb-1 ${okFab ? 'bg-emerald-600 text-white' : 'bg-amber-100 text-amber-800'}">${okFab ? '✓' : '•'} Cotação Fab.</span> <span class="inline-block px-2 py-0.5 rounded text-[10px] font-bold mb-1 ${okPag ? 'bg-emerald-600 text-white' : 'bg-amber-100 text-amber-800'}">${okPag ? '✓' : '•'} Comprovante</span> <button type="button" onclick="abrirModalDossiePorProposta('${p.numero}')" class="block text-emerald-700 hover:underline font-bold text-[11px] mt-0.5"> <i class="fa-solid fa-file-circle-plus mr-1"></i> Inserir Cotação Fab. & Comprovante </button> </td> <td class="p-3 space-y-1.5"> <div> ${aprovada ? `<span class="px-2.5 py-1 bg-emerald-100 text-emerald-900 rounded-full text-[10px] font-bold">APROVADA (${p.poVinculado || 'PO'})</span>` : p.status === 'RECUSADA' ? `<span class="px-2.5 py-1 bg-rose-100 text-rose-800 rounded-full text-[10px] font-bold">RECUSADA</span>` : p.status === 'CANCELADA' ? `<span class="px-2.5 py-1 bg-zinc-200 text-zinc-800 rounded-full text-[10px] font-bold">CANCELADA</span>` : `<span class="px-2.5 py-1 bg-amber-100 text-amber-900 rounded-full text-[10px] font-bold">PENDENTE DE APROVAÇÃO</span>`} </div> <button type="button" onclick="abrirJanelaRastreabilidadeVenda('${p.numero}')" class="bg-sky-50 hover:bg-sky-100 text-sky-900 border border-sky-200 px-2.5 py-1 rounded-lg text-[10px] font-bold inline-flex items-center gap-1.5 transition"> <i class="fa-solid fa-truck-fast text-sky-600"></i> Ver Status & Rastreabilidade </button> </td> <td class="p-3 text-right space-y-1 min-w-[195px]"> <div class="flex gap-1 justify-end"> <button type="button" onclick="gerarPDFPropostaExecutiva('${p.numero}')" class="bg-zinc-900 hover:bg-zinc-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-semibold"> <i class="fa-solid fa-file-pdf mr-1 text-rose-400"></i> PDF </button> <button type="button" onclick="editarPropostaExistente('${p.numero}')" class="bg-sky-600 hover:bg-sky-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-semibold"> <i class="fa-solid fa-pen-to-square mr-1"></i> Editar </button> </div> ${!aprovada && !inativa && usuarioEhDiretorDrones() ? ` <button type="button" onclick="aprovarPropostaEGerarPO('${p.numero}')" class="w-full bg-emerald-600 hover:bg-emerald-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-semibold block"> <i class="fa-solid fa-check mr-1"></i> Aprovar & Gerar P.O. </button> ` : ''} ${!inativa ? ` <div class="flex gap-1 justify-end pt-0.5"> <button type="button" onclick="recusarOuCancelarProposta('${p.numero}', 'RECUSADA')" class="bg-amber-100 hover:bg-amber-200 text-amber-900 px-2 py-1 rounded-lg text-[10px] font-semibold">Recusar</button> <button type="button" onclick="recusarOuCancelarProposta('${p.numero}', 'CANCELADA')" class="bg-rose-100 hover:bg-rose-200 text-rose-800 px-2 py-1 rounded-lg text-[10px] font-semibold">Cancelar</button> </div> ` : ''} </td> </tr> `; }).join(''); } // 2. TABELA DE PURCHASE ORDERS (C/ RASTREAMENTO E DOSSIÊ VENDAS) const tbodyPO = document.getElementById('tabela-pos'); if (tbodyPO) { tbodyPO.innerHTML = db.pos.map(p => { const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); const endEntrega = p.enderecoEntrega || (cli ? cli.endereco : '-'); const a = p.anexosPO || {}; const trk = p.tracking || {}; const defEtapa = obterDefEtapaTracking(trk.etapaCodigo); const cancelado = p.status === 'Cancelado'; const badgeAnexo = (ok, nome) => `<span class="inline-block px-2 py-0.5 rounded text-[10px] font-bold mr-1 mb-1 ${ok ? 'bg-emerald-600 text-white' : 'bg-amber-100 text-amber-800'}">${ok ? '✓' : '•'} ${nome}</span>`; return ` <tr class="hover:bg-zinc-50 ${cancelado ? 'bg-rose-50/40 opacity-75' : ''}"> <td class="p-3 font-mono font-bold">${p.numero}<span class="block text-[10px] font-normal text-zinc-400">Ref. Cotação: ${p.refVenda}</span></td> <td class="p-3 font-semibold">${cli ? cli.nome : '-'}<span class="block text-[10px] font-normal text-zinc-500 mt-0.5"><i class="fa-solid fa-location-dot text-rose-500 mr-1"></i><b>Entrega:</b> ${endEntrega}</span></td> <td class="p-3 font-semibold">${p.fornecedor}<span class="block text-[10px] font-normal text-zinc-500">${p.produto}</span></td> <td class="p-3 font-bold">${p.valor || fmtBRL(p.valorNum)}</td> <td class="p-3"> ${badgeAnexo(!!a.proposta, '1. Proposta')} ${badgeAnexo(!!a.cotacaoDRS, '2. Cot. Fab.')} ${badgeAnexo(!!a.comprovantePagto, '3. Comprovante')} <button type="button" onclick="abrirModalAnexosPO('${p.numero}')" class="block mt-1 text-emerald-700 hover:underline font-bold text-[11px]"> <i class="fa-solid fa-folder-open mr-1"></i> Ver / Editar Dossiê (Vendas) </button> </td> <td class="p-3"> <span class="px-2 py-0.5 border rounded-lg font-bold text-[10px] ${cancelado ? 'bg-rose-100 text-rose-800' : 'bg-sky-50 text-sky-900 border-sky-200'}">${defEtapa.curto}</span> <span class="block text-[11px] font-semibold text-zinc-800 mt-1"><i class="fa-solid fa-location-dot text-rose-500 mr-1"></i>${trk.localAtual || '-'}</span> <span class="block text-[10px] text-zinc-500">${trk.processoAtual || ''}</span> </td> <td class="p-3 text-right space-y-1 min-w-[185px]"> <div class="flex gap-1 justify-end"> ${usuarioPodeAtualizarLogistica() ? ` <button type="button" onclick="abrirModalTrackingPO('${p.numero}')" class="bg-sky-600 hover:bg-sky-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-bold"> <i class="fa-solid fa-route mr-1"></i> Editar Rastreio </button> ` : `<span class="px-2 py-1 bg-zinc-100 text-zinc-500 rounded text-[9px] font-bold">Rastreio: Logística</span>`} <button type="button" onclick="gerarPDFPurchaseOrder('${p.numero}')" class="bg-zinc-900 hover:bg-zinc-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-semibold"> PDF </button> ${usuarioPodeAtualizarLogistica() ? ` <button type="button" onclick="editarPOExistente('${p.numero}')" class="bg-zinc-200 hover:bg-zinc-300 text-zinc-800 px-2 py-1 rounded-lg text-[10px] font-semibold"> Editar </button> ` : ''} </div> ${!cancelado ? ` <div class="flex gap-1 justify-end pt-0.5"> <button type="button" onclick="cancelarPedidoPO('${p.numero}')" class="bg-rose-100 text-rose-800 px-2 py-1 rounded-lg text-[10px] font-semibold">Cancelar P.O.</button> </div> ` : ''} </td> </tr> `; }).join(''); } renderTrackingCards(); renderEstoque(); // 3. RENDER DIRETORIA E GRÁFICOS if (usuarioEhDiretoria()) { try { const { macro: fin } = calcularDemonstrativosConsolidadosHolding(); const elKpiCli = document.getElementById('dir-kpi-clientes'); if (elKpiCli) elKpiCli.textContent = db.clientes.length; const elSubFrota = document.getElementById('dir-kpi-frota-sub'); if (elSubFrota) elSubFrota.textContent = `${db.sns.length} equip. S.N. na frota`; const posAtivos = db.pos.filter(p => p.status !== 'Cancelado').length; const posDesemb = db.pos.filter(p => p.status !== 'Cancelado' && obterDefEtapaTracking(p.tracking?.etapaCodigo).ordem >= 8).length; const elKpiPos = document.getElementById('dir-kpi-pos'); if (elKpiPos) elKpiPos.textContent = `${posAtivos} / ${posDesemb}`; const elKpiEst = document.getElementById('dir-kpi-estoque'); if (elKpiEst) elKpiEst.textContent = fmtBRL(fin.estoquesFisicoContabil); const elKpiCx = document.getElementById('dir-kpi-caixa'); if (elKpiCx) elKpiCx.textContent = fmtBRL(fin.saldoCaixa); const elRast = document.getElementById('dir-rastreabilidade-tbody'); if (elRast) { elRast.innerHTML = db.propostas.length === 0 ? '<tr><td colspan="4" class="p-3 text-zinc-400 italic">Nenhum pedido emitido ainda.</td></tr>' : db.propostas.map(p => { const cli = db.clientes.find(c => Number(c.id) === Number(p.clienteId)); const poVinc = db.pos.find(po => po.numero === p.poVinculado || String(po.refVenda) === String(p.numero)); const infoTrk = poVinc?.tracking ? `• <b>Onde:</b> ${poVinc.tracking.localAtual} (${obterDefEtapaTracking(poVinc.tracking.etapaCodigo).curto})` : ''; const vend = p.vendedor || p.criadoPor || '-'; const com4 = Number(p.comissaoValor ?? (Number(p.total || 0) * TAXA_COMISSAO_VENDEDOR)); return ` <tr> <td class="p-2 font-mono font-bold">Nº ${p.numero}</td> <td class="p-2">${cli ? cli.nome : '-'}<span class="block text-[10px] text-emerald-800 font-bold">Resp.: ${vend}</span></td> <td class="p-2 font-bold">${fmtBRL(p.total)}${usuarioEhResponsavelPelaVenda(vend) ? `<span class="block text-[10px] text-emerald-800 font-mono">${vend}: ${fmtBRL(com4)}</span>` : ''}</td> <td class="p-2"> <span class="px-2 py-0.5 rounded text-[10px] font-bold ${p.status === 'APROVADA' ? 'bg-emerald-100 text-emerald-800' : p.status === 'RECUSADA' || p.status === 'CANCELADA' ? 'bg-rose-100 text-rose-800' : 'bg-amber-100 text-amber-800'}">${p.status}</span> ${p.poVinculado ? `<span class="font-mono text-[10px] text-indigo-700 ml-1">(${p.poVinculado})</span>` : ''} <span class="text-[10px] text-sky-800 block mt-0.5">${infoTrk}</span> ${etiquetaRastreabilidade(p)} </td> </tr> `; }).join(''); } const elMan = document.getElementById('dir-manuais-tbody'); if (elMan) { elMan.innerHTML = db.manuais.map(m => ` <tr> <td class="p-2 font-semibold"> <button type="button" onclick="abrirModalVisualizarManual(${m.id})" class="text-left hover:text-sky-700 hover:underline font-bold">${m.titulo} (${m.pn})</button> </td> <td class="p-2 font-mono">${m.versao}</td> <td class="p-2"> <span class="px-2 py-0.5 rounded text-[10px] font-bold ${m.aprovadoDiretoria ? 'bg-emerald-100 text-emerald-800' : 'bg-amber-100 text-amber-800'}"> ${m.aprovadoDiretoria ? 'Homologado' : 'Pendente'} </span> </td> <td class="p-2 text-right space-x-1"> <button type="button" onclick="abrirModalVisualizarManual(${m.id})" class="px-2 py-1 bg-sky-600 hover:bg-sky-700 text-white rounded text-[10px] font-bold"> <i class="fa-solid fa-eye mr-1"></i>Ver </button> <button type="button" onclick="alternarAprovacaoDiretoria(${m.id})" class="px-2 py-1 bg-zinc-900 text-white rounded text-[10px]"> ${m.aprovadoDiretoria ? 'Revogar' : 'Homologar'} </button> </td> </tr> `).join(''); } renderAuditoria(); renderGraficosDiretoria(); } catch (errDir) { console.error(errDir); } } if (usuarioTemVisaoFinanceira()) { try { renderFinanceiro(); } catch (eFin) { console.error(eFin); } } // 4. DEMAIS TABELAS document.getElementById('tabela-oportunidades').innerHTML = db.oportunidades.map(o => { const comEst = Number(o.valor || 0) * TAXA_COMISSAO_VENDEDOR; const mostrarResp = usuarioEhResponsavelPelaVenda(o.criadoPor); return `<tr><td class="p-2.5 font-mono font-bold">${o.id}</td><td class="p-2.5">${o.titulo}</td><td class="p-2.5">${o.categoria}</td><td class="p-2.5">${o.etapa}</td><td class="p-2.5 font-bold">${fmtBRL(o.valor)}${mostrarResp ? `<span class="block text-[10px] text-emerald-800 font-mono">${o.criadoPor}: ${fmtBRL(comEst)}</span>` : ''}</td><td class="p-2.5">${etiquetaRastreabilidade(o)}</td></tr>`; }).join(''); document.getElementById('tabela-clientes').innerHTML = db.clientes.map(c => `<tr class="hover:bg-zinc-50"><td class="p-3 font-bold">${c.nome}</td><td class="p-3 font-mono">${c.cnpj}</td><td class="p-3">${c.contato} • ${c.email || ''} (${c.whatsapp})</td><td class="p-3">${c.drone} (${c.qtd}x)</td><td class="p-3">${c.endereco} (${c.cidade || ''})${etiquetaRastreabilidade(c)}</td><td class="p-3 text-right"><button type="button" onclick="editarClienteExistente('${c.id}')" class="px-2.5 py-1 bg-sky-600 hover:bg-sky-700 text-white rounded-lg text-[10px] font-semibold">Editar</button></td></tr>`).join(''); document.getElementById('lista-anamneses-container').innerHTML = db.anamneses.map(a => `<div class="p-3 border rounded-lg text-xs"><b>${a.id} (${a.statusQualificacao}):</b> ${a.palavrasCliente} | Drone: ${a.droneModelo} | MTOW: ${a.pesoMtow}kg ${etiquetaRastreabilidade(a)}</div>`).join(''); document.getElementById('tabela-vendas').innerHTML = db.vendas.map(v => { const cli = db.clientes.find(c => Number(c.id) === Number(v.clienteId)); const poVinc = db.pos.find(po => po.numero === v.poGerado || String(po.refVenda) === String(v.propostaNumero)); const propVinc = db.propostas.find(pr => String(pr.numero) === String(v.propostaNumero)); const trk = poVinc?.tracking; const vend = v.vendedor || propVinc?.vendedor || v.criadoPor || 'Lucas Azevedo'; const comissao4 = Number(v.comissaoValor ?? propVinc?.comissaoValor ?? (Number(v.valorAutorizado || 0) * TAXA_COMISSAO_VENDEDOR)); const mostrarParaResponsavel = usuarioEhResponsavelPelaVenda(vend); return ` <tr> <td class="p-3 font-mono font-bold">${v.id}<span class="block text-[10px] font-normal text-zinc-400">Cotação Nº ${v.propostaNumero}</span></td> <td class="p-3 font-semibold">${cli ? cli.nome : '-'}<span class="block text-[10px] font-bold text-emerald-800"><i class="fa-solid fa-user-tie mr-1 text-emerald-600"></i>Resp.: ${vend}</span></td> <td class="p-3">${v.produto}</td> <td class="p-3 font-bold"> ${fmtBRL(v.valorAutorizado)} ${mostrarParaResponsavel ? ` <span class="block text-[10px] font-mono font-extrabold text-emerald-800 mt-0.5"> <i class="fa-solid fa-user-check mr-1 text-emerald-600"></i>${vend}: ${fmtBRL(comissao4)} </span> ` : ''} </td> <td class="p-3"> <span class="px-2.5 py-0.5 rounded-full bg-sky-100 text-sky-900 font-bold text-[10px] inline-block mr-2"> ${trk ? obterDefEtapaTracking(trk.etapaCodigo).curto : v.status} </span> <button type="button" onclick="abrirJanelaRastreabilidadeVenda('${v.propostaNumero}')" class="bg-zinc-900 hover:bg-zinc-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-bold inline-flex items-center gap-1"> <i class="fa-solid fa-magnifying-glass-location text-sky-400"></i> Status & Rastreabilidade </button> </td> <td class="p-3 text-right"> ${v.poGerado ? `<button type="button" onclick="abrirModalAnexosPO('${v.poGerado}')" class="bg-emerald-600 hover:bg-emerald-700 text-white px-3 py-1.5 rounded-lg text-[11px] font-bold"><i class="fa-solid fa-paperclip mr-1"></i> Dossiê Vendas (${v.poGerado})</button>` : `<button type="button" onclick="abrirModalDossiePorProposta('${v.propostaNumero}')" class="bg-emerald-600 text-white px-3 py-1.5 rounded-lg text-[11px] font-bold">Dossiê Vendas</button>`} </td> </tr> `; }).join(''); document.getElementById('tabela-fornecedores').innerHTML = db.fornecedores.map(f => `<tr><td class="p-2 font-bold">${f.nome}</td><td class="p-2">${f.pais}</td><td class="p-2">${f.contato}</td><td class="p-2">${f.moeda}</td><td class="p-2">${etiquetaRastreabilidade(f)}</td></tr>`).join(''); document.getElementById('tabela-sns').innerHTML = db.sns.map(s => { const cli = db.clientes.find(c => Number(c.id) === Number(s.clienteId)); return `<tr class="hover:bg-zinc-50"><td class="p-3"><button onclick="abrirFichaSN('${s.sn}')" class="font-mono font-bold text-sky-700 underline">${s.sn}</button></td><td class="p-3 font-mono">${s.pn}</td><td class="p-3 font-semibold">${cli ? cli.nome : s.clienteId}</td><td class="p-3 font-mono">${s.snAnv}</td><td class="p-3">${fmtDataBR(s.instalacao)}</td><td class="p-3 font-bold">${s.horas}h <span class="block text-[10px] text-amber-700 font-semibold">${s.acionamentos || 0} acionamento(s)</span></td><td class="p-3 text-[11px]">Shelf: ${fmtDataBR(s.shelfLife)}<br><b>Rev 12m: ${fmtDataBR(s.revisao)}</b></td><td class="p-3">${etiquetaRastreabilidade(s)}</td><td class="p-3 text-right space-x-1"><button type="button" onclick="editarSNExistente('${s.sn}')" class="px-2.5 py-1 bg-sky-600 text-white rounded-lg text-[10px] font-semibold">Editar</button>${usuarioEhDiretorDrones() ? `<button type="button" onclick="removerSN('${s.sn}')" class="px-2 py-1 bg-rose-100 text-rose-700 rounded-lg text-[10px]">Excluir</button>` : ''}</td></tr>`; }).join(''); document.getElementById('tabela-integracoes').innerHTML = db.integracoes.map(i => `<tr><td class="p-2 font-mono font-bold">${i.sn}</td><td class="p-2">${fmtDataBR(i.data)}</td><td class="p-2">${i.responsavel}</td><td class="p-2">${i.testes}</td><td class="p-2 font-semibold">${i.status}</td><td class="p-2">${etiquetaRastreabilidade(i)}</td></tr>`).join(''); document.getElementById('tabela-logbook').innerHTML = db.logbook.map(l => `<tr><td class="p-3 font-mono">${fmtDataBR(l.data)}</td><td class="p-3 font-mono font-bold">${l.sn}</td><td class="p-3 font-semibold">${l.tipo}</td><td class="p-3">${l.horas}h</td><td class="p-3">${l.desc}</td><td class="p-3">${etiquetaRastreabilidade(l)}</td><td class="p-3 text-right">${usuarioEhDiretorDrones() ? `<button onclick="removerLogbook(${l.id})" class="px-2 py-1 bg-rose-100 text-rose-700 rounded-lg text-[10px]">Excluir</button>` : '—'}</td></tr>`).join(''); document.getElementById('tabela-demandas').innerHTML = (db.demandasPosVenda || []).map(d => { const cli = db.clientes.find(c => Number(c.id) === Number(d.clienteId)); return ` <tr class="hover:bg-zinc-50"> <td class="p-3 font-mono font-bold text-zinc-950">${d.protocolo}<span class="block text-[10px] font-normal text-zinc-500">Prazo: ${fmtDataBR(d.prazo)} • ${d.prioridade}</span></td> <td class="p-3 font-semibold">${cli ? cli.nome : '-'}<span class="block text-[10px] font-mono text-sky-700">S.N.: ${d.sn} (${d.horas || 0}h)</span></td> <td class="p-3"><span class="px-2 py-0.5 rounded bg-amber-100 text-amber-900 font-bold text-[10px]">${d.categoria}</span></td> <td class="p-3">${d.desc}</td> <td class="p-3 text-[11px] text-emerald-800 font-semibold">${d.integracoesResumo || 'Integrado à Frota S.N. e Logbook'} ${d.osVinculada ? `<button type="button" onclick="switchSubTab('pos', 'mro_drones')" class="block underline text-sky-700 font-bold mt-0.5">Abrir ${d.osVinculada}</button>` : ''}</td> <td class="p-3"><span class="px-2 py-0.5 rounded bg-zinc-100 text-zinc-800 font-bold text-[10px]">${d.status}</span>${etiquetaRastreabilidade(d)}</td> </tr> `; }).join(''); renderDefense(); renderDesenvolvimento(); renderPosVendaPOClientes(); renderOrdensServicoMRO(); document.getElementById('tabela-pns').innerHTML = db.pns.map(p => `<tr><td class="p-2 font-mono font-bold">${p.pn}</td><td class="p-2">${p.desc}</td><td class="p-2">${etiquetaRastreabilidade(p)}</td></tr>`).join(''); document.getElementById('tabela-manuais').innerHTML = db.manuais.map(m => { const temArquivo = !!(m.pdf && m.pdf.length > 30); return ` <tr class="hover:bg-zinc-50"> <td class="p-3 font-mono font-bold">${m.pn}</td> <td class="p-3 font-semibold"> <button type="button" onclick="abrirModalVisualizarManual(${m.id})" class="text-left hover:text-sky-700 hover:underline font-bold"> ${m.titulo} </button> <span class="block text-[10px] text-zinc-500 font-normal">${m.obs || ''}</span> </td> <td class="p-3 font-mono">${m.versao}<span class="block text-[10px] text-zinc-400">${fmtDataBR(m.data)}</span></td> <td class="p-3"> <div class="flex flex-wrap items-center gap-1.5"> <button type="button" onclick="abrirModalVisualizarManual(${m.id})" class="px-2.5 py-1 bg-sky-600 hover:bg-sky-700 text-white rounded-lg text-[10px] font-bold shadow-sm inline-flex items-center gap-1"> <i class="fa-solid fa-book-open-reader"></i> Ver Manual </button> <button type="button" onclick="abrirManualEmNovaJanela(${m.id})" class="px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-white rounded-lg text-[10px] font-semibold inline-flex items-center gap-1" title="Abrir em Nova Aba"> <i class="fa-solid fa-up-right-from-square"></i> </button> ${temArquivo ? `<span class="px-1.5 py-0.5 rounded bg-emerald-50 text-emerald-800 border border-emerald-200 text-[9px] font-bold">PDF Anexo</span>` : `<span class="px-1.5 py-0.5 rounded bg-zinc-100 text-zinc-600 text-[9px] font-semibold">Doc. Sistema</span>`} </div> </td> <td class="p-3">${etiquetaRastreabilidade(m)}</td> <td class="p-3"> <span class="px-2.5 py-1 rounded-full text-[10px] font-extrabold ${m.aprovadoDiretoria ? 'bg-emerald-100 text-emerald-900' : 'bg-amber-100 text-amber-900'}"> ${m.aprovadoDiretoria ? '✓ Homologado' : '⏳ Pendente'} </span> ${m.homologadoPor ? `<span class="block text-[10px] text-emerald-700 font-semibold mt-0.5">${m.homologadoPor}</span>` : ''} </td> <td class="p-3 text-right"> ${usuarioEhDiretorDrones() ? `<button type="button" onclick="alternarAprovacaoDiretoria(${m.id})" class="px-2.5 py-1 bg-zinc-900 hover:bg-zinc-700 text-white rounded-lg text-[10px] font-bold">${m.aprovadoDiretoria ? 'Revogar' : 'Homologar'}</button>` : '<span class="text-[10px] text-zinc-400">Restrito Diretoria</span>'} </td> </tr> `; }).join(''); renderRelatorioMensal(); } // ========================================================= // MÓDULO EXCLUSIVO: LES DEFENSE (CONSULTORIA & CONTRATOS ANTI-DRONE) // ========================================================= function obterClienteDefensePorId(id) { return (db.defense?.clientes || []).find(c => Number(c.id) === Number(id)); } function salvarClienteDefense(e) { e.preventDefault(); if (!exigirPermissao(['diretoria', 'diretor_defense', 'defense', 'defense_tecnico'], 'Cadastrar / Editar Cliente LES Defense')) return; garantirEstruturaDB(); const editId = document.getElementById('def-cli-edit-id').value.trim(); const contato = document.getElementById('def-cli-contato').value.trim(); const nome = document.getElementById('def-cli-nome').value.trim(); const fantasia = document.getElementById('def-cli-fantasia').value.trim(); const cnpj = document.getElementById('def-cli-cnpj').value.trim(); const whatsapp = document.getElementById('def-cli-whatsapp').value.trim(); const email = document.getElementById('def-cli-email').value.trim(); const cidade = document.getElementById('def-cli-cidade').value.trim(); const segmento = document.getElementById('def-cli-segmento').value; const perimetro = document.getElementById('def-cli-perimetro').value.trim(); const endereco = document.getElementById('def-cli-endereco').value.trim(); const obs = document.getElementById('def-cli-obs').value.trim(); const agora = carimboHoraAtual(); if (editId) { const idx = db.defense.clientes.findIndex(c => String(c.id) === String(editId)); if (idx >= 0) { db.defense.clientes[idx] = { ...db.defense.clientes[idx], contato, nome, fantasia, cnpj, whatsapp, email, cidade, segmento, perimetro, endereco, obs, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('LES_DEFENSE', `Editou Cliente Defense "${nome}" (${cnpj})`, cnpj); cancelarEdicaoClienteDefense(); saveDB(); return; } } if (db.defense.clientes.some(c => normalizarTexto(c.cnpj) === normalizarTexto(cnpj))) { alert('Já existe um cliente cadastrado na base da LES Defense com este CNPJ/Identificação. Use o botão "Editar" na tabela abaixo.'); return; } const novoCli = { id: Date.now(), contato, nome, fantasia, cnpj, whatsapp, email, cidade, segmento, perimetro, endereco, obs, criadoPor: sessaoAtual.nome, criadoEm: agora }; db.defense.clientes.push(novoCli); registrarAuditoria('LES_DEFENSE', `Cadastrou novo Cliente exclusivo LES Defense: "${nome}" (${cnpj})`, cnpj); cancelarEdicaoClienteDefense(); saveDB(); } function editarClienteDefense(id) { const c = obterClienteDefensePorId(id); if (!c) return; abrirAbaDefenseEspecifica('clientes'); document.getElementById('def-cli-edit-id').value = c.id; document.getElementById('def-cli-contato').value = c.contato || ''; document.getElementById('def-cli-nome').value = c.nome || ''; document.getElementById('def-cli-fantasia').value = c.fantasia || ''; document.getElementById('def-cli-cnpj').value = c.cnpj || ''; document.getElementById('def-cli-whatsapp').value = c.whatsapp || ''; document.getElementById('def-cli-email').value = c.email || ''; document.getElementById('def-cli-cidade').value = c.cidade || ''; document.getElementById('def-cli-segmento').value = c.segmento || 'Infraestrutura Crítica / Indústria & Logística'; document.getElementById('def-cli-perimetro').value = c.perimetro || ''; document.getElementById('def-cli-endereco').value = c.endereco || ''; document.getElementById('def-cli-obs').value = c.obs || ''; document.getElementById('def-cli-mode-badge').textContent = `MODO DE EDIÇÃO ATIVO • CLIENTE DEFENSE`; document.getElementById('def-cli-form-titulo').textContent = `Editando Cliente LES Defense: ${c.nome}`; document.getElementById('btn-submit-def-cli').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Alterações (${c.nome})`; document.getElementById('btn-cancelar-edicao-def-cli').classList.remove('hidden'); document.getElementById('card-form-def-cliente')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoClienteDefense() { const f = document.getElementById('form-def-cliente'); if (f) f.reset(); const elId = document.getElementById('def-cli-edit-id'); if (elId) elId.value = ''; const badge = document.getElementById('def-cli-mode-badge'); if (badge) badge.textContent = 'Carteira Comercial B2B • LES Defense'; const tit = document.getElementById('def-cli-form-titulo'); if (tit) tit.textContent = 'Cadastrar / Editar Cliente Corporativo ou Institucional — LES Defense'; const btn = document.getElementById('btn-submit-def-cli'); if (btn) btn.innerHTML = `<i class="fa-solid fa-building-shield mr-1.5 text-emerald-400"></i> Salvar Cliente LES Defense`; document.getElementById('btn-cancelar-edicao-def-cli')?.classList.add('hidden'); } function removerClienteDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Excluir Cliente da Base LES Defense (Diretoria Defense / Holding)')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${id}`); db.defense.clientes = (db.defense.clientes || []).filter(c => String(c.id) !== String(id)); registrarAuditoria('LES_DEFENSE', `Removeu cliente ${id} da base LES Defense`, String(id)); saveDB(); } function salvarConsultoriaDefense(e) { e.preventDefault(); garantirEstruturaDB(); const editId = document.getElementById('def-cons-edit-id').value.trim(); const id = document.getElementById('def-cons-id').value.trim(); const data = document.getElementById('def-cons-data').value || new Date().toISOString().slice(0, 10); const clienteId = Number(document.getElementById('def-cons-cliente').value); const status = document.getElementById('def-cons-status').value; const planta = document.getElementById('def-cons-planta').value.trim(); const raio = document.getElementById('def-cons-raio').value.trim(); const risco = document.getElementById('def-cons-risco').value; const arquitetura = document.getElementById('def-cons-arquitetura').value; const ambiente = document.getElementById('def-cons-ambiente').value.trim(); const parecer = document.getElementById('def-cons-parecer').value.trim(); const responsavel = document.getElementById('def-cons-resp').value.trim() || sessaoAtual.nome; const agora = carimboHoraAtual(); if (editId) { const idx = db.defense.consultorias.findIndex(c => String(c.id) === String(editId)); if (idx >= 0) { db.defense.consultorias[idx] = { ...db.defense.consultorias[idx], id, data, clienteId, status, planta, raio, risco, arquitetura, ambiente, parecer, responsavel, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('LES_DEFENSE', `Editou Diagnóstico / Site Survey ${id} (${planta})`, id); cancelarEdicaoConsultoriaDefense(); saveDB(); return; } } if (db.defense.consultorias.some(c => String(c.id) === String(id))) { alert('Já existe um Diagnóstico / Site Survey com este código. Use o botão "Editar" na lista abaixo.'); return; } db.defense.consultorias.unshift({ id, data, clienteId, status, planta, raio, risco, arquitetura, ambiente, parecer, responsavel, criadoPor: sessaoAtual.nome, criadoEm: agora }); registrarAuditoria('LES_DEFENSE', `Registrou Consultoria / Site Survey ${id} (${planta})`, id); cancelarEdicaoConsultoriaDefense(); saveDB(); } function editarConsultoriaDefense(id) { const c = (db.defense?.consultorias || []).find(x => String(x.id) === String(id)); if (!c) return; abrirAbaDefenseEspecifica('consultoria'); document.getElementById('def-cons-edit-id').value = c.id; document.getElementById('def-cons-id').value = c.id; document.getElementById('def-cons-data').value = c.data || ''; document.getElementById('def-cons-cliente').value = c.clienteId; document.getElementById('def-cons-status').value = c.status || '1. Vistoria Agendada'; document.getElementById('def-cons-planta').value = c.planta || ''; document.getElementById('def-cons-raio').value = c.raio || ''; document.getElementById('def-cons-risco').value = c.risco || 'Espionagem Industrial / Filmagem Não Autorizada'; document.getElementById('def-cons-arquitetura').value = c.arquitetura || 'Sensor RF Passivo + Remote ID (Detecção de Drone e Posição do Piloto)'; document.getElementById('def-cons-ambiente').value = c.ambiente || ''; document.getElementById('def-cons-parecer').value = c.parecer || ''; document.getElementById('def-cons-resp').value = c.responsavel || ''; document.getElementById('def-cons-mode-badge').textContent = `MODO DE EDIÇÃO ATIVO • ${c.id}`; document.getElementById('def-cons-form-titulo').textContent = `Editando Consultoria & Diagnóstico ${c.id}`; document.getElementById('btn-submit-def-cons').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Alterações (${c.id})`; document.getElementById('btn-cancelar-edicao-def-cons').classList.remove('hidden'); document.getElementById('card-form-def-consultoria')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoConsultoriaDefense() { const form = document.getElementById('form-def-consultoria'); if (form) form.reset(); document.getElementById('def-cons-edit-id').value = ''; document.getElementById('def-cons-mode-badge').textContent = 'Diagnóstico B2B • Apenas Equipamentos de Detecção Passiva (Conformidade ANATEL / DECEA)'; document.getElementById('def-cons-form-titulo').textContent = 'Registrar / Editar Consultoria & Diagnóstico de Vulnerabilidade (Site Survey)'; document.getElementById('btn-submit-def-cons').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Consultoria / Diagnóstico`; document.getElementById('btn-cancelar-edicao-def-cons').classList.add('hidden'); } function removerConsultoriaDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Excluir Diagnóstico LES Defense (Diretoria Defense / Holding)')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${id}`); db.defense.consultorias = db.defense.consultorias.filter(c => String(c.id) !== String(id)); registrarAuditoria('LES_DEFENSE', `Excluiu Diagnóstico ${id}`, id); saveDB(); } function gerarContratoDeConsultoriaDefense(diagId) { const c = (db.defense?.consultorias || []).find(x => String(x.id) === String(diagId)); if (!c) return; abrirAbaDefenseEspecifica('contratos'); cancelarEdicaoContratoDefense(); document.getElementById('def-cont-id').value = 'DEF-' + Date.now().toString().slice(-4); document.getElementById('def-cont-data').value = new Date().toISOString().slice(0, 10); document.getElementById('def-cont-cliente').value = c.clienteId; const selDiag = document.getElementById('def-cont-diag-ref'); if (selDiag) selDiag.value = c.id; document.getElementById('def-cont-modalidade').value = 'Monitoramento Contínuo 24/7 (Locação de Sensores de Detecção + Alertas)'; document.getElementById('def-cont-escopo').value = `Implantação e monitoramento passivo em ${c.planta} (${c.raio}) utilizando arquitetura: ${c.arquitetura}.`; document.getElementById('card-form-def-contrato')?.scrollIntoView({ behavior: 'smooth' }); } function recalcularPreviewContratoDefense() { const valorCheio = parseFloat(document.getElementById('def-cont-valor')?.value) || 0; const forma = document.getElementById('def-cont-forma-pagamento')?.value || 'PIX'; const elParcSelect = document.getElementById('def-cont-parcelas-cartao'); const boxParcDef = elParcSelect?.parentElement; if (boxParcDef) { if (forma === 'CARTAO') boxParcDef.classList.remove('hidden'); else boxParcDef.classList.add('hidden'); } const parcelas = parseInt(elParcSelect?.value) || 4; const vend = document.getElementById('def-cont-vendedor')?.value || sessaoAtual.nome || 'Jean Natividade'; const calc = calcularValoresPagamentoProposta(valorCheio, forma, parcelas); const elComPrev = document.getElementById('def-cont-comissao-preview'); if (elComPrev) { const boxComDef = elComPrev.parentElement; if (usuarioEhResponsavelPelaVenda(vend)) { if (boxComDef) boxComDef.classList.remove('hidden'); elComPrev.value = `${fmtBRL(calc.comissaoVendedor4)} (${vend})`; } else { if (boxComDef) boxComDef.classList.add('hidden'); } } const elPrev = document.getElementById('def-cont-calculo-preview'); if (elPrev) { if (forma === 'PIX') { elPrev.value = `Modo PIX c/ 5% de Desconto (-${fmtBRL(calc.descontoPix5)}) -> Valor Final: ${fmtBRL(calc.totalFinal)}`; } else if (forma === 'CARTAO') { elPrev.value = `Modo Cartão: ${calc.parcelasCartao}x de ${fmtBRL(calc.valorParcelaCartao)}${calc.parcelasCartao <= 4 ? ' sem juros' : ''} -> Total: ${fmtBRL(calc.totalFinal)}`; } else { elPrev.value = `Modo Dinheiro à Vista -> Valor Final: ${fmtBRL(calc.totalFinal)}`; } } } function sincronizarContratoDefenseComContabil(ct, idAntigo = null) { if (!ct) return false; let alterou = false; const idAtual = String(ct.id); const idBusca = idAntigo ? String(idAntigo) : idAtual; const docRefNovo = `DEFENSE-${idAtual}`; const cli = obterClienteDefensePorId(ct.clienteId); const nomeCli = cli ? cli.nome : 'Cliente Corporativo Defense'; const valorFinal = Number(ct.valor || 0); const vendedorNome = ct.vendedor || ct.criadoPor || 'Jean Natividade'; ct.vendedor = vendedorNome; ct.comissaoPerc = 4; const valorComissao4 = Number((valorFinal * TAXA_COMISSAO_VENDEDOR).toFixed(2)); ct.comissaoValor = valorComissao4; const sufixoPagto = ct.resumoPagamento ? ` • [${ct.resumoPagamento}]` : ''; const agora = carimboHoraAtual(); const lanc = (db.defense?.financeiro?.lancamentos || []).find(l => l.id === `FDEF-${idBusca}` || l.id === `FDEF-${idAtual}` || l.doc === `DEFENSE-${idBusca}` || l.doc === docRefNovo ); const lancComDef = (db.defense?.financeiro?.lancamentos || []).find(l => l.id === `FDEF-COM-${idBusca}` || l.id === `FDEF-COM-${idAtual}` ); if (ct.status === 'Cancelado / Recusado') { if (lanc && !lanc.estornado) { lanc.estornado = true; lanc.status = 'ESTORNADO'; lanc.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Cancelamento Contrato Defense)`; lanc.modificadoEm = agora; alterou = true; } if (lancComDef && !lancComDef.estornado) { lancComDef.estornado = true; lancComDef.status = 'ESTORNADO'; lancComDef.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Cancelamento Contrato Defense)`; lancComDef.modificadoEm = agora; alterou = true; } return alterou; } if (lanc) { const novoLiq = Number((valorFinal - Number(lanc.impostos || 0)).toFixed(2)); const novaDesc = `[LES Defense] ${ct.modalidade} — ${ct.id}${sufixoPagto}`; if (Number(lanc.bruto) !== valorFinal || Number(lanc.liquido) !== novoLiq || lanc.doc !== docRefNovo || lanc.entidade !== nomeCli || lanc.desc !== novaDesc) { lanc.id = `FDEF-${idAtual}`; lanc.doc = docRefNovo; lanc.entidade = nomeCli; lanc.desc = novaDesc; lanc.bruto = valorFinal; lanc.liquido = novoLiq; lanc.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Edição Contrato Defense)`; lanc.modificadoEm = agora; alterou = true; } ct.integradoFin = true; } else { integrarContratoDefenseAoFinanceiro(ct); alterou = true; } if (lancComDef) { const novaEntCom = `Vendedor Defense: ${vendedorNome} (4%)`; const novaDescCom = `Comissão Comercial Defense 4% (${vendedorNome}) — Contrato ${ct.id} (4% s/ ${fmtBRL(valorFinal)})`; if (Number(lancComDef.bruto) !== valorComissao4 || Number(lancComDef.liquido) !== valorComissao4 || lancComDef.entidade !== novaEntCom || lancComDef.desc !== novaDescCom) { lancComDef.id = `FDEF-COM-${idAtual}`; lancComDef.doc = docRefNovo; lancComDef.entidade = novaEntCom; lancComDef.desc = novaDescCom; lancComDef.bruto = valorComissao4; lancComDef.liquido = valorComissao4; lancComDef.modificadoPor = `${sessaoAtual.nome || 'Sistema'} (Sync Comissão Defense 4%)`; lancComDef.modificadoEm = agora; alterou = true; } } else if (valorComissao4 > 0) { const hojeISO = ct.data || new Date().toISOString().slice(0, 10); db.defense.financeiro.lancamentos.unshift({ id: `FDEF-COM-${idAtual}`, natureza: 'DESPESA_OP', categoria: 'Comissões Comerciais Defense (4%)', desc: `Comissão Comercial Defense 4% (${vendedorNome}) — Contrato ${ct.id} (4% s/ ${fmtBRL(valorFinal)})`, entidade: `Vendedor Defense: ${vendedorNome} (4%)`, doc: docRefNovo, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: valorComissao4, impostos: 0, liquido: valorComissao4, status: 'PREVISTO', conta: 'Banco Exclusivo LES Defense', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Comissão 4% Defense)`, criadoEm: agora }); alterou = true; } return alterou; } function salvarContratoDefense(e) { e.preventDefault(); garantirEstruturaDB(); const editId = document.getElementById('def-cont-edit-id').value.trim(); const id = document.getElementById('def-cont-id').value.trim(); const data = document.getElementById('def-cont-data').value || new Date().toISOString().slice(0, 10); const clienteId = Number(document.getElementById('def-cont-cliente').value); const vendedor = document.getElementById('def-cont-vendedor')?.value || sessaoAtual.nome || 'Jean Natividade'; const diagRef = document.getElementById('def-cont-diag-ref').value; const modalidade = document.getElementById('def-cont-modalidade').value; const vigencia = document.getElementById('def-cont-vigencia').value.trim(); const periodicidade = document.getElementById('def-cont-periodicidade').value; const escopo = document.getElementById('def-cont-escopo').value.trim(); const valorCheio = parseFloat(document.getElementById('def-cont-valor').value) || 0; const formaPagamento = document.getElementById('def-cont-forma-pagamento')?.value || 'PIX'; const parcelasCartao = parseInt(document.getElementById('def-cont-parcelas-cartao')?.value) || 4; const calcPagto = calcularValoresPagamentoProposta(valorCheio, formaPagamento, parcelasCartao); const valor = calcPagto.totalFinal; const status = document.getElementById('def-cont-status').value; const agora = carimboHoraAtual(); if (editId) { const idx = db.defense.contratos.findIndex(x => String(x.id) === String(editId)); if (idx >= 0) { db.defense.contratos[idx] = { ...db.defense.contratos[idx], id, data, clienteId, vendedor, comissaoPerc: 4, comissaoValor: calcPagto.comissaoVendedor4, diagRef, modalidade, vigencia, periodicidade, escopo, valorCheio: calcPagto.valorCheio, formaPagamento: calcPagto.formaPagamento, parcelasCartao: calcPagto.parcelasCartao, descontoPix: calcPagto.descontoPix, resumoPagamento: calcPagto.resumoPagamento, valor, status, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; sincronizarContratoDefenseComContabil(db.defense.contratos[idx], editId); registrarAuditoria('LES_DEFENSE', `Editou Proposta/Contrato Defense ${id} (Vendedor: ${vendedor} • Conta: ${fmtBRL(valor)} • Comissão 4%: ${fmtBRL(calcPagto.comissaoVendedor4)})`, id); cancelarEdicaoContratoDefense(); saveDB(); return; } } if (db.defense.contratos.some(x => String(x.id) === String(id))) { alert('Já existe uma Proposta/Contrato Defense com este número. Use o botão "Editar" na tabela abaixo.'); return; } const novo = { id, data, clienteId, vendedor, comissaoPerc: 4, comissaoValor: calcPagto.comissaoVendedor4, diagRef, modalidade, vigencia, periodicidade, escopo, valorCheio: calcPagto.valorCheio, formaPagamento: calcPagto.formaPagamento, parcelasCartao: calcPagto.parcelasCartao, descontoPix: calcPagto.descontoPix, resumoPagamento: calcPagto.resumoPagamento, valor, status, integradoFin: false, criadoPor: sessaoAtual.nome, criadoEm: agora }; db.defense.contratos.unshift(novo); if (diagRef) { const diagObj = db.defense.consultorias.find(d => String(d.id) === String(diagRef)); if (diagObj) diagObj.status = '4. Convertido em Projeto/Contrato'; } if (status !== 'Cancelado / Recusado') { integrarContratoDefenseAoFinanceiro(novo); } registrarAuditoria('LES_DEFENSE', `Registrou Proposta/Contrato Defense ${id} (Vendedor: ${vendedor} • Conta: ${fmtBRL(valor)} • Comissão 4%: ${fmtBRL(calcPagto.comissaoVendedor4)})`, id); cancelarEdicaoContratoDefense(); saveDB(); } function editarContratoDefense(id) { const ct = (db.defense?.contratos || []).find(x => String(x.id) === String(id)); if (!ct) return; abrirAbaDefenseEspecifica('contratos'); document.getElementById('def-cont-edit-id').value = ct.id; document.getElementById('def-cont-id').value = ct.id; document.getElementById('def-cont-data').value = ct.data || ''; document.getElementById('def-cont-cliente').value = ct.clienteId; const elVendDef = document.getElementById('def-cont-vendedor'); if (elVendDef) elVendDef.value = ct.vendedor || ct.criadoPor || 'Jean Natividade'; document.getElementById('def-cont-diag-ref').value = ct.diagRef || ''; document.getElementById('def-cont-modalidade').value = ct.modalidade || 'Consultoria Técnica & Diagnóstico de Espaço Aéreo (Site Survey)'; document.getElementById('def-cont-vigencia').value = ct.vigencia || '12 Meses'; document.getElementById('def-cont-periodicidade').value = ct.periodicidade || 'Valor Único (Projeto / Consultoria)'; document.getElementById('def-cont-escopo').value = ct.escopo || ''; document.getElementById('def-cont-valor').value = ct.valorCheio !== undefined ? ct.valorCheio : (ct.valor || 0); const elFormaDef = document.getElementById('def-cont-forma-pagamento'); if (elFormaDef) elFormaDef.value = ct.formaPagamento || 'PIX'; const elParcDef = document.getElementById('def-cont-parcelas-cartao'); if (elParcDef) elParcDef.value = String(ct.parcelasCartao || 4); document.getElementById('def-cont-status').value = ct.status || 'Proposta em Análise'; recalcularPreviewContratoDefense(); document.getElementById('def-cont-mode-badge').textContent = `MODO DE EDIÇÃO ATIVO • ${ct.id}`; document.getElementById('def-cont-form-titulo').textContent = `Editando Proposta / Contrato ${ct.id} (Atualiza Contábil Defense)`; document.getElementById('btn-submit-def-cont').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Alterações (${ct.id}) & Atualizar Contábil`; document.getElementById('btn-cancelar-edicao-def-cont').classList.remove('hidden'); document.getElementById('card-form-def-contrato')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoContratoDefense() { const form = document.getElementById('form-def-contrato'); if (form) form.reset(); document.getElementById('def-cont-edit-id').value = ''; const elFormaDef = document.getElementById('def-cont-forma-pagamento'); if (elFormaDef) elFormaDef.value = 'PIX'; const elParcDef = document.getElementById('def-cont-parcelas-cartao'); if (elParcDef) elParcDef.value = '4'; const elVendDef = document.getElementById('def-cont-vendedor'); if (elVendDef && sessaoAtual.nome) elVendDef.value = sessaoAtual.nome; recalcularPreviewContratoDefense(); document.getElementById('def-cont-mode-badge').textContent = 'Comercial B2B • Contratos Alimentam Exclusivamente o Financeiro da Subsidiária LES Defense'; document.getElementById('def-cont-form-titulo').textContent = 'Emitir / Editar Proposta ou Contrato de Serviço LES Defense'; document.getElementById('btn-submit-def-cont').innerHTML = `<i class="fa-solid fa-file-signature mr-1.5 text-emerald-400"></i> Salvar Proposta / Contrato LES Defense`; document.getElementById('btn-cancelar-edicao-def-cont').classList.add('hidden'); } function integrarContratoDefenseAoFinanceiro(ct) { garantirEstruturaDB(); const docRef = `DEFENSE-${ct.id}`; if (db.defense.financeiro.lancamentos.some(l => (l.id === `FDEF-${ct.id}`) && !l.estornado)) { sincronizarContratoDefenseComContabil(ct, ct.id); ct.integradoFin = true; return; } const cli = obterClienteDefensePorId(ct.clienteId); const hojeISO = ct.data || new Date().toISOString().slice(0, 10); const sufixoPagto = ct.resumoPagamento ? ` • [${ct.resumoPagamento}]` : ''; const vendedorNome = ct.vendedor || ct.criadoPor || 'Jean Natividade'; const valorFinal = Number(ct.valor || 0); const valorComissao4 = Number((valorFinal * TAXA_COMISSAO_VENDEDOR).toFixed(2)); const agora = carimboHoraAtual(); db.defense.financeiro.lancamentos.unshift({ id: `FDEF-${ct.id}`, natureza: 'RECEITA', categoria: 'Consultoria Técnica & Site Survey', desc: `[LES Defense] ${ct.modalidade} — ${ct.id}${sufixoPagto}`, entidade: cli ? cli.nome : 'Cliente Corporativo Defense', doc: docRef, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: valorFinal, impostos: 0, liquido: valorFinal, status: 'PREVISTO', conta: 'Banco Exclusivo LES Defense', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Defense->Fin)`, criadoEm: agora }); if (valorComissao4 > 0) { db.defense.financeiro.lancamentos.unshift({ id: `FDEF-COM-${ct.id}`, natureza: 'DESPESA_OP', categoria: 'Comissões Comerciais Defense (4%)', desc: `Comissão Comercial Defense 4% (${vendedorNome}) — Contrato ${ct.id} (4% s/ ${fmtBRL(valorFinal)})`, entidade: `Vendedor Defense: ${vendedorNome} (4%)`, doc: docRef, competencia: hojeISO, vencimento: adicionarMesesData(hojeISO, 1), bruto: valorComissao4, impostos: 0, liquido: valorComissao4, status: 'PREVISTO', conta: 'Banco Exclusivo LES Defense', conciliado: false, estornado: false, criadoPor: `${sessaoAtual.nome || 'Sistema'} (Auto Comissão 4% Defense)`, criadoEm: agora }); } ct.integradoFin = true; } function aprovarContratoDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Aprovar Contrato e Lançar no Financeiro LES Defense (Exclusivo Diretor Defense / Holding)')) return; const ct = (db.defense?.contratos || []).find(x => String(x.id) === String(id)); if (!ct) return; ct.status = 'Aprovado / Contrato Ativo'; ct.aprovadoPor = sessaoAtual.nome; ct.aprovadoEm = carimboHoraAtual(); integrarContratoDefenseAoFinanceiro(ct); if (ct.diagRef) { const diagObj = db.defense.consultorias.find(d => String(d.id) === String(ct.diagRef)); if (diagObj) diagObj.status = '4. Convertido em Projeto/Contrato'; } registrarAuditoria('LES_DEFENSE', `Aprovou Contrato ${ct.id} (${fmtBRL(ct.valor)} • Comissão 4%: ${fmtBRL(ct.comissaoValor)}) e lançou no Financeiro LES Defense`, ct.id); saveDB(); } function removerContratoDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Excluir Contrato LES Defense')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${id}`); db.defense.contratos = db.defense.contratos.filter(c => String(c.id) !== String(id)); registrarAuditoria('LES_DEFENSE', `Excluiu Contrato Defense ${id}`, id); saveDB(); } // ========================================================= // ESTOQUE EXCLUSIVO DA SUBSIDIÁRIA LES DEFENSE // ========================================================= function recalcularPreviewEstoqueDefense() { const qtd = parseFloat(document.getElementById('def-est-qtd')?.value) || 0; const custo = parseFloat(document.getElementById('def-est-custo')?.value) || 0; const el = document.getElementById('def-est-total-preview'); if (el) el.value = fmtBRL(qtd * custo); } function registrarReflexoContabilEstoqueDefense(refId, naturezaFin, valor, descricao, docRef, dataComp) { garantirEstruturaDB(); const hojeISO = dataComp || new Date().toISOString().slice(0, 10); db.defense.financeiro.lancamentos.unshift({ id: `FDEF-${refId}-${Date.now().toString().slice(-3)}`, natureza: naturezaFin, categoria: 'Sensores Passivos RF / Remote ID / Hardware', desc: descricao, entidade: 'Almoxarifado Exclusivo LES Defense', doc: docRef, competencia: hojeISO, vencimento: hojeISO, bruto: valor, impostos: 0, liquido: valor, status: 'CONTABIL_ESTOQUE', conta: '1.1.03-DEF - Estoques LES Defense', conciliado: true, estornado: false, criadoPor: `${sessaoAtual.nome} (Auto Estoque Defense->Contábil)`, criadoEm: carimboHoraAtual() }); } function salvarItemEstoqueDefense(e) { e.preventDefault(); if (!exigirPermissao(['diretoria', 'diretor_defense', 'defense', 'defense_tecnico'], 'Gerenciar Estoque Exclusivo LES Defense')) return; garantirEstruturaDB(); const editId = document.getElementById('def-est-edit-id').value.trim(); const pn = document.getElementById('def-est-pn').value.trim(); const desc = document.getElementById('def-est-desc').value.trim(); const categoria = document.getElementById('def-est-categoria').value; const qtd = parseInt(document.getElementById('def-est-qtd').value) || 0; const qtdMin = parseInt(document.getElementById('def-est-min').value) || 0; const sn = document.getElementById('def-est-sn').value.trim(); const status = document.getElementById('def-est-status').value; const entrada = document.getElementById('def-est-entrada').value || new Date().toISOString().slice(0, 10); const validade = document.getElementById('def-est-validade').value; const local = document.getElementById('def-est-local').value.trim(); const docOrigem = document.getElementById('def-est-doc').value.trim() || 'INV-DEF'; const custoUnit = parseFloat(document.getElementById('def-est-custo').value) || 0; const valorTotal = Number((qtd * custoUnit).toFixed(2)); const agora = carimboHoraAtual(); if (editId) { const idx = db.defense.estoque.findIndex(x => String(x.id) === String(editId)); if (idx >= 0) { const ant = db.defense.estoque[idx]; const valorAnt = Number(ant.qtd || 0) * Number(ant.custoUnit || 0); db.defense.estoque[idx] = { ...ant, pn, desc, categoria, qtd, qtdMin, sn, status, entrada, validade, local, docOrigem, custoUnit, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; const dif = valorTotal - valorAnt; if (Math.abs(dif) > 0.01) { registrarReflexoContabilEstoqueDefense( editId, dif > 0 ? 'COMPRA_ESTOQUE' : 'DESPESA_OP', Math.abs(dif), `Ajuste Contábil de Inventário Defense (${pn})`, docOrigem, entrada ); } registrarAuditoria('LES_DEFENSE', `Editou equipamento do Estoque Defense ${editId} (${pn}) — Total: ${fmtBRL(valorTotal)}`, editId); cancelarEdicaoEstoqueDefense(); saveDB(); return; } } const itemDefExistente = db.defense.estoque.find(x => normalizarTexto(x.pn) === normalizarTexto(pn)); let novoId = 'EST-DEF-' + Date.now().toString().slice(-4); if (itemDefExistente) { novoId = itemDefExistente.id; itemDefExistente.qtd = Number(itemDefExistente.qtd || 0) + qtd; if (sn && !String(itemDefExistente.sn || '').includes(sn)) { itemDefExistente.sn = itemDefExistente.sn ? `${itemDefExistente.sn} / ${sn}` : sn; } if (custoUnit > 0) itemDefExistente.custoUnit = custoUnit; if (status) itemDefExistente.status = status; if (local) itemDefExistente.local = local; itemDefExistente.modificadoPor = sessaoAtual.nome; itemDefExistente.modificadoEm = agora; } else { db.defense.estoque.unshift({ id: novoId, pn, desc, categoria, qtd, qtdMin, sn, status, entrada, validade, local, docOrigem, custoUnit, criadoPor: sessaoAtual.nome, criadoEm: agora }); } if (valorTotal > 0) { registrarReflexoContabilEstoqueDefense( novoId, 'COMPRA_ESTOQUE', valorTotal, `Entrada Estoque LES Defense: ${qtd}x ${pn} (${desc})`, `${novoId} / ${docOrigem}`, entrada ); } registrarAuditoria('LES_DEFENSE', `Cadastrou equipamento no Estoque Defense ${novoId} (${pn} • ${fmtBRL(valorTotal)})`, novoId); cancelarEdicaoEstoqueDefense(); saveDB(); } function editarItemEstoqueDefense(id) { const it = (db.defense?.estoque || []).find(x => String(x.id) === String(id)); if (!it) return; abrirAbaDefenseEspecifica('estoque'); document.getElementById('def-est-edit-id').value = it.id; document.getElementById('def-est-pn').value = it.pn || ''; document.getElementById('def-est-desc').value = it.desc || ''; document.getElementById('def-est-categoria').value = it.categoria || 'Sensor RF Passivo / Remote ID'; document.getElementById('def-est-qtd').value = it.qtd || 0; document.getElementById('def-est-min').value = it.qtdMin || 1; document.getElementById('def-est-sn').value = it.sn || ''; document.getElementById('def-est-status').value = it.status || 'Disponível na Base Defense'; document.getElementById('def-est-entrada').value = it.entrada || ''; document.getElementById('def-est-validade').value = it.validade || ''; document.getElementById('def-est-local').value = it.local || ''; document.getElementById('def-est-doc').value = it.docOrigem || ''; document.getElementById('def-est-custo').value = it.custoUnit || 0; recalcularPreviewEstoqueDefense(); document.getElementById('def-est-mode-badge').textContent = `MODO DE EDIÇÃO ATIVO • ${it.id}`; document.getElementById('def-est-form-titulo').textContent = `Editando Equipamento Defense ${it.id} (${it.pn})`; document.getElementById('btn-submit-def-est').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Alterações (${it.id})`; document.getElementById('btn-cancelar-edicao-def-est').classList.remove('hidden'); document.getElementById('card-form-def-estoque')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoEstoqueDefense() { const form = document.getElementById('form-def-estoque'); if (form) form.reset(); document.getElementById('def-est-edit-id').value = ''; document.getElementById('def-est-mode-badge').textContent = 'Almoxarifado Técnico • LES Defense (Conta Contábil 1.1.03-DEF)'; document.getElementById('def-est-form-titulo').textContent = 'Cadastrar / Editar Equipamento ou Componente no Estoque LES Defense'; document.getElementById('btn-submit-def-est').innerHTML = `<i class="fa-solid fa-boxes-packing mr-1.5 text-emerald-400"></i> Salvar no Estoque Defense & Contabilizar`; document.getElementById('btn-cancelar-edicao-def-est').classList.add('hidden'); recalcularPreviewEstoqueDefense(); } function removerItemEstoqueDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Excluir Item do Estoque Defense (Diretoria Defense / Holding)')) return; db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${id}`); db.defense.estoque = (db.defense.estoque || []).filter(x => String(x.id) !== String(id)); registrarAuditoria('LES_DEFENSE', `Removeu item ${id} do Estoque Defense`, id); saveDB(); } function gerarPDFAuditoriaEstoqueDefense() { const itens = db.defense?.estoque || []; const totalUnidades = itens.reduce((a, b) => a + Number(b.qtd || 0), 0); const valorContabilTotal = calcularValorContabilTotalEstoqueDefense(); const agoraStr = carimboHoraAtual(); const linhasHTML = itens.map((it, idx) => { const sub = Number(it.qtd || 0) * Number(it.custoUnit || 0); return ` <tr> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:center; font-family:monospace;">${String(idx + 1).padStart(2, '0')}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; font-family:monospace; font-weight:700;">${it.pn}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8;"><b>${it.desc}</b><br><span style="font-size:10px; color:#52525b;">${it.categoria} • Local: ${it.local}</span></td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; font-family:monospace; font-size:10.5px;">${it.sn || '—'}<br><b>${it.status || ''}</b></td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:center; font-weight:800;">${it.qtd}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right;">${fmtBRL(it.custoUnit)}</td> <td style="padding:8px 10px; border-bottom:1px solid #d4d4d8; text-align:right; font-weight:800;">${fmtBRL(sub)}</td> </tr> `; }).join(''); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Auditoria de Estoque — Subsidiária LES Defense</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 15mm 16mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } table { width: 100%; border-collapse: collapse; font-size: 11px; } th { background: #09090b; color: #ffffff; text-transform: uppercase; font-size: 9.5px; padding: 8px 10px; text-align: left; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>SUBSIDIÁRIA LES DEFENSE</b> • Relatório Oficial de Auditoria de Estoque Tático & Sensores</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:16px;"> ${obterLogoHTMLDocumento('SUBSIDIÁRIA LES DEFENSE • ALMOXARIFADO DE SENSORES PASSIVOS & HARDWARE TÁTICO')} <div style="text-align:right;"> <div style="font-size:15px; font-weight:900; text-transform:uppercase;">AUDITORIA FÍSICA E CONTÁBIL DE ESTOQUE — LES DEFENSE</div> <div style="font-size:11px; margin-top:4px;">CONTA 1.1.03-DEF: <b style="color:#047857;">${fmtBRL(valorContabilTotal)}</b> (${totalUnidades} un.) | ${agoraStr}</div> </div> </div> <table style="border:1px solid #d4d4d8;"> <thead> <tr> <th style="text-align:center;">#</th> <th>P/N DEFENSE</th> <th>EQUIPAMENTO / CATEGORIA / LOCAL</th> <th>S.N. & STATUS</th> <th style="text-align:center;">QTD</th> <th style="text-align:right;">CUSTO UNIT.</th> <th style="text-align:right;">TOTAL CONTÁBIL</th> </tr> </thead> <tbody>${linhasHTML}</tbody> </table> </div> <div style="margin-top:24px; display:grid; grid-template-columns:1fr 1fr; gap:32px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>RESPONSÁVEL ESTOQUE / OPERAÇÕES LES DEFENSE</b><br>Lucas Pires / Jean Natividade </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>DIRETORIA HOLDING LES AVIATION</b><br>Conciliação do Balanço da Subsidiária </div> </div> </div> </body> </html> `); win.document.close(); } // ========================================================= // FINANCEIRO EXCLUSIVO DA SUBSIDIÁRIA LES DEFENSE // ========================================================= function salvarLancamentoFinanceiroDefense(e) { e.preventDefault(); if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Registrar Lançamento no Financeiro LES Defense (Exclusivo Diretor Defense / Holding)')) return; garantirEstruturaDB(); const natureza = document.getElementById('def-fin-natureza').value; const categoria = document.getElementById('def-fin-categoria').value; const desc = document.getElementById('def-fin-desc').value.trim(); const entidade = document.getElementById('def-fin-entidade').value.trim(); const doc = document.getElementById('def-fin-doc').value.trim(); const competencia = document.getElementById('def-fin-competencia').value || new Date().toISOString().slice(0, 10); const vencimento = document.getElementById('def-fin-vencimento').value || competencia; const bruto = parseFloat(document.getElementById('def-fin-bruto').value) || 0; const impostos = parseFloat(document.getElementById('def-fin-impostos').value) || 0; const status = document.getElementById('def-fin-status').value; const conta = document.getElementById('def-fin-conta').value.trim() || 'Banco Exclusivo LES Defense'; const id = 'FDEF-' + Date.now().toString().slice(-5); db.defense.financeiro.lancamentos.unshift({ id, natureza, categoria, desc, entidade, doc, competencia, vencimento, bruto, impostos, liquido: bruto - impostos, status, conta, conciliado: status === 'REALIZADO', estornado: false, criadoPor: sessaoAtual.nome, criadoEm: carimboHoraAtual() }); registrarAuditoria('LES_DEFENSE', `Lançou registro financeiro Defense ${id} (${fmtBRL(bruto - impostos)})`, id); e.target.reset(); saveDB(); } function conciliarLancamentoDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Conciliar Lançamento Financeiro Defense (Exclusivo Diretor Defense / Holding)')) return; const l = (db.defense?.financeiro?.lancamentos || []).find(x => String(x.id) === String(id)); if (l && !l.estornado) { l.conciliado = !l.conciliado; l.status = l.conciliado ? 'REALIZADO' : 'PREVISTO'; l.modificadoPor = sessaoAtual.nome; l.modificadoEm = carimboHoraAtual(); registrarAuditoria('LES_DEFENSE', `Conciliou lançamento Defense ${id} (${l.status})`, id); saveDB(); } } function estornarLancamentoDefense(id) { if (!exigirPermissao(['diretoria', 'diretor_defense'], 'Estornar Lançamento Financeiro Defense (Exclusivo Diretor Defense / Holding)')) return; const l = (db.defense?.financeiro?.lancamentos || []).find(x => String(x.id) === String(id)); if (l && !l.estornado) { l.estornado = true; l.status = 'ESTORNADO'; l.modificadoPor = sessaoAtual.nome; l.modificadoEm = carimboHoraAtual(); registrarAuditoria('LES_DEFENSE', `Estornou lançamento Defense ${id}`, id); saveDB(); } } function renderDefense() { garantirEstruturaDB(); const consultorias = db.defense.consultorias || []; const contratos = db.defense.contratos || []; const itensEstDef = db.defense.estoque || []; const finDef = calcularDemonstrativosFinanceirosDefense(); const contratosAtivos = contratos.filter(c => c.status === 'Aprovado / Contrato Ativo' || c.status === 'Concluído / Entregue'); const receitaTotalDef = contratosAtivos.reduce((acc, c) => acc + Number(c.valor || 0), 0); const podeVerFinDefense = usuarioEhDiretorDefense(); const defSubtabsBar = document.getElementById('def-subtabs-bar'); if (defSubtabsBar) { if (usuarioUsaAbasCompletasDefense()) defSubtabsBar.classList.add('hidden'); else defSubtabsBar.classList.remove('hidden'); } const kpiEl = document.getElementById('def-kpis-grid'); if (kpiEl) { if (podeVerFinDefense) { kpiEl.innerHTML = ` <div class="les-card p-4 border-l-4 border-l-zinc-950"> <p class="text-[10px] font-bold uppercase text-zinc-500">Site Surveys / Diagnósticos</p> <p class="text-2xl font-extrabold text-zinc-950 mt-1">${consultorias.length}</p> <p class="text-[11px] text-zinc-400">Vistorias de espectro RF</p> </div> <div class="les-card p-4 border-l-4 border-l-emerald-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Contratos Ativos Defense</p> <p class="text-2xl font-extrabold text-emerald-700 mt-1">${contratosAtivos.length}</p> <p class="text-[11px] text-zinc-400">Contratado: ${fmtBRL(receitaTotalDef)}</p> </div> <div class="les-card p-4 border-l-4 border-l-amber-500"> <p class="text-[10px] font-bold uppercase text-zinc-500">Estoque Exclusivo Defense</p> <p class="text-xl font-extrabold text-amber-700 mt-1">${fmtBRL(finDef.estoquesFisicoContabil)}</p> <p class="text-[11px] text-zinc-400">${itensEstDef.length} lote(s) de sensores/equip.</p> </div> <div class="les-card p-4 border-l-4 border-l-sky-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Caixa Próprio Defense</p> <p class="text-xl font-extrabold text-sky-700 mt-1">${fmtBRL(finDef.saldoCaixa)}</p> <p class="text-[11px] text-zinc-400">A Receber: ${fmtBRL(finDef.contasReceber)}</p> </div> <div class="les-card p-4 border-l-4 border-l-teal-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Patrimônio Líquido Defense</p> <p class="text-xl font-extrabold text-teal-800 mt-1">${fmtBRL(finDef.patrimonioLiquido)}</p> <p class="text-[11px] text-zinc-400">Diretoria Defense (Lucas Pires)</p> </div> `; } else { const totalUnidadesSensores = itensEstDef.reduce((a, b) => a + Number(b.qtd || 0), 0); const minhaComissaoDef = contratos .filter(c => c.status !== 'Cancelado / Recusado' && (!sessaoAtual.nome || normalizarTexto(c.vendedor || c.criadoPor) === normalizarTexto(sessaoAtual.nome))) .reduce((acc, c) => acc + Number(c.comissaoValor ?? (Number(c.valor || 0) * TAXA_COMISSAO_VENDEDOR)), 0); const tarefasDefPendentes = obterTarefasDesenvolvimentoVisiveis().filter(t => !tarefaEstaConcluidaDev(t)).length; kpiEl.innerHTML = ` <div class="les-card p-4 border-l-4 border-l-zinc-950"> <p class="text-[10px] font-bold uppercase text-zinc-500">Site Surveys / Diagnósticos</p> <p class="text-2xl font-extrabold text-zinc-950 mt-1">${consultorias.length}</p> <p class="text-[11px] text-zinc-400">Vistorias técnicas de RF</p> </div> <div class="les-card p-4 border-l-4 border-l-emerald-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Projetos & Operações Ativas</p> <p class="text-2xl font-extrabold text-emerald-700 mt-1">${contratosAtivos.length}</p> <p class="text-[11px] text-zinc-400">Monitoramento & Implantação</p> </div> <div class="les-card p-4 border-l-4 border-l-amber-500"> <p class="text-[10px] font-bold uppercase text-amber-700">Meu Retorno Defense</p> <p class="text-xl font-extrabold text-amber-700 mt-1 font-mono">${fmtBRL(minhaComissaoDef)}</p> <p class="text-[11px] text-zinc-400">4% s/ propostas & contratos Defense</p> </div> <div class="les-card p-4 border-l-4 border-l-sky-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Sensores & Equip. Defense</p> <p class="text-2xl font-extrabold text-sky-700 mt-1">${totalUnidadesSensores} un.</p> <p class="text-[11px] text-zinc-400">${itensEstDef.length} categoria(s) no estoque</p> </div> <div class="les-card p-4 border-l-4 border-l-teal-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Engenharia & P&D Ativo</p> <p class="text-2xl font-extrabold text-teal-800 mt-1">${tarefasDefPendentes}</p> <p class="text-[11px] text-zinc-400">Etapas técnicas em andamento</p> </div> `; } } const selDiag = document.getElementById('def-cont-diag-ref'); if (selDiag) { const valAnt = selDiag.value; selDiag.innerHTML = `<option value="">-- Sem diagnóstico vinculado --</option>` + consultorias.map(c => `<option value="${c.id}">${c.id} — ${c.planta}</option>`).join(''); if (valAnt) selDiag.value = valAnt; } const termoCons = normalizarTexto(document.getElementById('filtro-def-cons')?.value || ''); const consFiltradas = consultorias.filter(c => { if (!termoCons) return true; const cli = obterClienteDefensePorId(c.clienteId); return normalizarTexto(`${c.id} ${cli?.nome || ''} ${c.planta} ${c.risco} ${c.arquitetura} ${c.status}`).includes(termoCons); }); const listaConsEl = document.getElementById('lista-def-consultorias'); if (listaConsEl) { listaConsEl.innerHTML = consFiltradas.length === 0 ? `<div class="p-6 text-center text-zinc-400 text-xs italic border rounded-xl">Nenhuma consultoria ou diagnóstico cadastrado.</div>` : consFiltradas.map(c => { const cli = obterClienteDefensePorId(c.clienteId) || {}; return ` <div class="p-4 rounded-xl border border-zinc-200 bg-white hover:border-zinc-300 shadow-sm space-y-3 text-xs"> <div class="flex flex-wrap justify-between items-center gap-2 border-b pb-2.5"> <div class="flex flex-wrap items-center gap-2"> <span class="font-mono font-black text-xs bg-zinc-950 text-emerald-400 px-2.5 py-1 rounded-lg">${c.id}</span> <b class="text-zinc-950 text-sm">${cli.nome || 'Empresa Cliente'}</b> <span class="text-zinc-500">• ${c.planta}</span> <span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-emerald-100 text-emerald-900">${c.status}</span> </div> <div class="flex flex-wrap gap-1.5"> <button type="button" onclick="editarConsultoriaDefense('${c.id}')" class="bg-sky-600 hover:bg-sky-700 text-white px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-pen-to-square mr-1"></i> Editar </button> <button type="button" onclick="gerarPDFLaudoConsultoriaDefense('${c.id}')" class="bg-zinc-950 hover:bg-zinc-800 text-white px-3.5 py-1.5 rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-file-pdf text-emerald-400 mr-1"></i> Laudo PDF (LES Defense) </button> <button type="button" onclick="gerarContratoDeConsultoriaDefense('${c.id}')" class="bg-emerald-600 hover:bg-emerald-700 text-white px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-file-signature mr-1"></i> Gerar Proposta/Contrato </button> ${usuarioEhDiretorDefense() ? `<button type="button" onclick="removerConsultoriaDefense('${c.id}')" class="bg-rose-100 hover:bg-rose-200 text-rose-700 px-2.5 py-1.5 rounded-lg text-xs font-semibold">Excluir</button>` : ''} </div> </div> <div class="grid grid-cols-1 md:grid-cols-3 gap-3 bg-zinc-50 p-3 rounded-lg border text-[11px]"> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Perímetro & Risco Mapeado</span> <b>${c.raio}</b><br><span class="text-rose-700 font-semibold">${c.risco}</span> </div> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Arquitetura de Detecção Recomendada</span> <b class="text-emerald-900">${c.arquitetura}</b><br><span class="text-zinc-500">Ambiente: ${c.ambiente}</span> </div> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">Parecer Técnico & Responsável</span> <span>${c.parecer}</span> <span class="block text-[10px] text-zinc-400 mt-1">Resp.: <b>${c.responsavel}</b> (${fmtDataBR(c.data)})</span> </div> </div> </div> `; }).join(''); } const tbodyCont = document.getElementById('tabela-def-contratos'); if (tbodyCont) { tbodyCont.innerHTML = contratos.length === 0 ? `<tr><td colspan="7" class="p-4 text-center text-zinc-400 italic">Nenhuma proposta ou contrato LES Defense cadastrado.</td></tr>` : contratos.map(ct => { const cli = obterClienteDefensePorId(ct.clienteId) || {}; const aprovado = ct.status === 'Aprovado / Contrato Ativo' || ct.status === 'Concluído / Entregue'; const vend = ct.vendedor || ct.criadoPor || 'Jean Natividade'; const com4 = Number(ct.comissaoValor ?? (Number(ct.valor || 0) * TAXA_COMISSAO_VENDEDOR)); return ` <tr class="hover:bg-zinc-50"> <td class="p-3 font-mono font-bold text-zinc-950">${ct.id}<span class="block text-[10px] font-normal text-zinc-400">${fmtDataBR(ct.data)}</span></td> <td class="p-3 font-semibold"> ${cli.nome || '-'} <span class="block text-[10px] font-bold text-emerald-800 mt-0.5"><i class="fa-solid fa-user-tie mr-1"></i>Vendedor: ${vend}</span> <span class="block text-[10px] font-mono text-zinc-400">${ct.diagRef ? 'Ref: ' + ct.diagRef : 'Direto'}</span> </td> <td class="p-3"><b class="text-zinc-900 block">${ct.modalidade}</b><span class="text-[11px] text-zinc-500">${ct.escopo}</span></td> <td class="p-3 font-semibold">${ct.vigencia}<span class="block text-[10px] font-normal text-zinc-500">${ct.periodicidade}</span></td> <td class="p-3 font-extrabold text-zinc-950"> ${fmtBRL(ct.valor)} ${usuarioEhResponsavelPelaVenda(vend) ? `<span class="inline-block mt-1 px-2 py-0.5 rounded bg-emerald-50 border border-emerald-300 text-emerald-900 font-mono text-[10px] font-extrabold"><i class="fa-solid fa-user-check mr-1 text-emerald-600"></i>${vend}: ${fmtBRL(com4)}</span>` : ''} ${ct.resumoPagamento ? `<span class="block text-[10px] font-semibold text-emerald-700 mt-0.5">${ct.resumoPagamento}</span>` : ''} </td> <td class="p-3"> <span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold ${aprovado ? 'bg-emerald-100 text-emerald-900' : ct.status === 'Cancelado / Recusado' ? 'bg-rose-100 text-rose-800' : 'bg-amber-100 text-amber-900'}">${ct.status}</span> ${ct.integradoFin ? `<span class="block text-[10px] text-emerald-700 font-bold mt-1"><i class="fa-solid fa-check-circle mr-1"></i>No Financeiro Defense</span>` : ''} ${etiquetaRastreabilidade(ct)} </td> <td class="p-3 text-right space-y-1 min-w-[190px]"> <div class="flex gap-1 justify-end"> <button type="button" onclick="gerarPDFPropostaContratoDefense('${ct.id}')" class="bg-zinc-950 hover:bg-zinc-800 text-white px-2.5 py-1 rounded-lg text-[10px] font-bold"> <i class="fa-solid fa-file-pdf text-emerald-400 mr-1"></i> PDF Defense </button> <button type="button" onclick="editarContratoDefense('${ct.id}')" class="bg-sky-600 hover:bg-sky-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-semibold"> Editar </button> </div> ${!aprovado && usuarioEhDiretorDefense() ? ` <button type="button" onclick="aprovarContratoDefense('${ct.id}')" class="w-full bg-emerald-600 hover:bg-emerald-700 text-white px-2.5 py-1 rounded-lg text-[10px] font-bold block"> <i class="fa-solid fa-check mr-1"></i> Aprovar & Lançar Fin. Defense </button> ` : ''} ${usuarioEhDiretorDefense() ? `<div class="flex justify-end pt-0.5"><button type="button" onclick="removerContratoDefense('${ct.id}')" class="bg-rose-100 text-rose-700 px-2 py-0.5 rounded text-[10px]">Excluir</button></div>` : ''} </td> </tr> `; }).join(''); } const termoEstDef = normalizarTexto(document.getElementById('filtro-def-estoque')?.value || ''); const estDefFiltrados = itensEstDef.filter(i => !termoEstDef || normalizarTexto(`${i.id} ${i.pn} ${i.desc} ${i.categoria} ${i.sn} ${i.local} ${i.status}`).includes(termoEstDef)); const tbodyEstDef = document.getElementById('tabela-def-estoque'); if (tbodyEstDef) { tbodyEstDef.innerHTML = estDefFiltrados.length === 0 ? `<tr><td colspan="7" class="p-4 text-center text-zinc-400 italic">Nenhum equipamento cadastrado no Estoque LES Defense.</td></tr>` : estDefFiltrados.map(i => { const sub = Number(i.qtd || 0) * Number(i.custoUnit || 0); return ` <tr class="hover:bg-zinc-50"> <td class="p-3 font-mono font-bold text-zinc-950">${i.pn}<span class="block text-[10px] font-normal text-zinc-400">${i.id} • Doc: ${i.docOrigem || '-'}</span></td> <td class="p-3 font-semibold">${i.desc}<span class="block text-[10px] font-normal text-emerald-700">${i.categoria}</span></td> <td class="p-3 font-mono text-[11px]"> <b>S.N.:</b> ${i.sn || '—'} <span class="block mt-0.5 px-2 py-0.5 rounded bg-emerald-50 text-emerald-900 font-sans font-bold text-[10px] inline-block">${i.status || 'Disponível'}</span> </td> <td class="p-3 text-[11px]">${i.local}<span class="block text-[10px] text-zinc-400">Aquisição: ${fmtDataBR(i.entrada)}</span></td> <td class="p-3 text-center whitespace-nowrap"> <span class="inline-flex items-center justify-center whitespace-nowrap px-2.5 py-1 rounded-lg font-extrabold text-xs ${Number(i.qtd) <= Number(i.qtdMin) ? 'bg-rose-100 text-rose-800' : 'bg-zinc-950 text-emerald-400'}">${i.qtd} ea</span> <span class="block text-[10px] text-zinc-400 mt-0.5 whitespace-nowrap">Mín: ${i.qtdMin || 0} ea</span> </td> <td class="p-3 text-right font-bold">${fmtBRL(sub)}<span class="block text-[10px] font-normal text-zinc-500">Unit: ${fmtBRL(i.custoUnit)}</span></td> <td class="p-3 text-right space-x-1"> <button type="button" onclick="editarItemEstoqueDefense('${i.id}')" class="px-2.5 py-1 bg-sky-600 text-white rounded-lg text-[10px] font-semibold">Editar</button> ${usuarioEhDiretorDefense() ? `<button type="button" onclick="removerItemEstoqueDefense('${i.id}')" class="px-2 py-1 bg-rose-100 text-rose-700 rounded-lg text-[10px] font-semibold">Excluir</button>` : ''} </td> </tr> `; }).join(''); } const clientesDef = db.defense.clientes || []; const termoCliDef = normalizarTexto(document.getElementById('filtro-def-clientes')?.value || ''); const cliDefFiltrados = clientesDef.filter(c => !termoCliDef || normalizarTexto(`${c.nome} ${c.fantasia || ''} ${c.cnpj} ${c.contato} ${c.cidade} ${c.segmento || ''} ${c.endereco}`).includes(termoCliDef)); const tbodyCliDef = document.getElementById('tabela-def-clientes'); if (tbodyCliDef) { tbodyCliDef.innerHTML = cliDefFiltrados.length === 0 ? `<tr><td colspan="6" class="p-4 text-center text-zinc-400 italic">Nenhum cliente cadastrado na base exclusiva da LES Defense.</td></tr>` : cliDefFiltrados.map(c => ` <tr class="hover:bg-zinc-50"> <td class="p-3 font-bold text-zinc-950">${c.nome}${c.fantasia ? `<span class="block text-[10px] font-normal text-zinc-500">${c.fantasia}</span>` : ''}</td> <td class="p-3 font-mono whitespace-nowrap">${c.cnpj}</td> <td class="p-3">${c.contato}<span class="block text-[10px] text-zinc-500">${c.email || ''} • ${c.whatsapp || ''}</span></td> <td class="p-3"><span class="px-2 py-0.5 rounded bg-emerald-50 text-emerald-900 font-semibold text-[10px] inline-block">${c.segmento || 'Corporativo'}</span><span class="block text-[10px] text-zinc-500 mt-0.5">${c.perimetro || ''}</span></td> <td class="p-3 text-[11px]">${c.endereco} (${c.cidade || ''})${etiquetaRastreabilidade(c)}</td> <td class="p-3 text-right whitespace-nowrap space-x-1"> <button type="button" onclick="editarClienteDefense('${c.id}')" class="px-2.5 py-1 bg-sky-600 hover:bg-sky-700 text-white rounded-lg text-[10px] font-semibold">Editar</button> ${usuarioEhDiretorDefense() ? `<button type="button" onclick="removerClienteDefense('${c.id}')" class="px-2 py-1 bg-rose-100 text-rose-700 rounded-lg text-[10px] font-semibold">Excluir</button>` : ''} </td> </tr> `).join(''); } } function gerarPDFLaudoConsultoriaDefense(diagId) { const c = (db.defense?.consultorias || []).find(x => String(x.id) === String(diagId)); if (!c) return; const cli = obterClienteDefensePorId(c.clienteId) || {}; const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Laudo Técnico LES Defense ${c.id}</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 15mm 17mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } .sec-title { background: #09090b; color: #ffffff; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.09em; padding: 6px 10px; margin-top: 14px; margin-bottom: 6px; border-radius: 4px; display:flex; justify-content:space-between; } .grid-table { width: 100%; border-collapse: collapse; font-size: 11px; } .grid-table td { border: 1px solid #d4d4d8; padding: 8px 10px; vertical-align: top; line-height: 1.5; } .lbl { display: block; font-size: 8.5px; font-weight: 800; text-transform: uppercase; color: #71717a; margin-bottom: 3px; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES DEFENSE</b> • Laudo Executivo de Consultoria & Diagnóstico Anti-Drone (${c.id})</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir Laudo</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:14px;"> ${obterLogoHTMLDocumento('SUBSIDIÁRIA LES DEFENSE • INTELIGÊNCIA DE ESPAÇO AÉREO & DETECÇÃO ANTI-DRONE')} <div style="text-align:right;"> <div style="font-size:14px; font-weight:900; text-transform:uppercase;">LAUDO DE CONSULTORIA & DIAGNÓSTICO DE ESPAÇO AÉREO (SITE SURVEY)</div> <div style="font-size:11px; font-family:monospace; font-weight:800; margin-top:5px; background:#f4f4f5; padding:3px 8px; border-radius:4px; border:1px solid #d4d4d8; display:inline-block;"> CÓDIGO: ${c.id} | DATA: ${fmtDataBR(c.data)} | STATUS: ${c.status} </div> </div> </div> <div class="sec-title"><span>01 IDENTIFICAÇÃO DA EMPRESA CLIENTE E UNIDADE AVALIADA</span><span>SITE SURVEY B2B</span></div> <table class="grid-table"> <tr> <td><span class="lbl">EMPRESA CLIENTE / RAZÃO SOCIAL</span><b>${cli.nome || '-'} (${cli.cnpj || '-'})</b></td> <td><span class="lbl">CONTATO CORPORATIVO</span><b>${cli.contato || '-'} • ${cli.whatsapp || '-'}</b></td> </tr> <tr> <td><span class="lbl">PLANTA / UNIDADE OPERACIONAL AVALIADA</span><b>${c.planta}</b></td> <td><span class="lbl">RAIO / PERÍMETRO DE PROTEÇÃO REQUERIDO</span><b>${c.raio}</b></td> </tr> </table> <div class="sec-title"><span>02 MAPEAMENTO DE RISCO E ANÁLISE DO AMBIENTE DE RADIOFREQUÊNCIA (RF)</span><span>DIAGNÓSTICO TÉCNICO</span></div> <table class="grid-table"> <tr> <td><span class="lbl">VETOR DE RISCO PRINCIPAL IDENTIFICADO</span><b style="color:#be123c;">${c.risco}</b></td> </tr> <tr> <td><span class="lbl">CARACTERIZAÇÃO DO AMBIENTE RF, TOPOGRAFIA E OBSTÁCULOS FÍSICOS</span>${c.ambiente}</td> </tr> </table> <div class="sec-title"><span>03 ARQUITETURA DE DETECÇÃO PASSIVA RECOMENDADA & PARECER DE ENGENHARIA</span><span>SOLUÇÃO DE DETECÇÃO</span></div> <table class="grid-table"> <tr> <td><span class="lbl">TECNOLOGIA DE SENSORES RECOMENDADA (APENAS DETECÇÃO PASSIVA)</span><b style="color:#047857; font-size:12px;">${c.arquitetura}</b></td> </tr> <tr> <td><span class="lbl">PARECER TÉCNICO, POSICIONAMENTO DE SENSORES E PROTOCOLO DE RESPOSTA</span><b>${c.parecer}</b></td> </tr> <tr> <td> <span class="lbl">CONFORMIDADE REGULATÓRIA (ANATEL / DECEA)</span> A solução dimensionada pela <b>LES Defense</b> baseia-se exclusivamente em <b>equipamentos de detecção e monitoramento passivo</b> (recepção de telemetria RF, Remote ID, confirmação óptica/térmica ou radar de baixa potência homologado), sem emissão de sinais de bloqueio ou interferência eletromagnética (jamming), operando em estrita conformidade com as normas da ANATEL e do DECEA. </td> </tr> </table> </div> <div style="margin-top:24px;"> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; margin-bottom:14px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>ENGENHARIA & CONSULTORIA LES DEFENSE</b><br>Responsável: ${c.responsavel} </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>RECEBIMENTO DO DIAGNÓSTICO PELO CLIENTE</b><br>${cli.nome || 'Representante Corporativo'} — Data: ___/___/______ </div> </div> <div style="border-top:2px solid #09090b; padding-top:8px; display:flex; justify-content:space-between; font-size:9.5px; color:#52525b; font-weight:600;"> <span>LES DEFENSE • DIVISÃO ANTI-DRONE & MONITORAMENTO DE ESPAÇO AÉREO</span> <span>Documento Confidencial • ${c.id}</span> </div> </div> </div> </body> </html> `); win.document.close(); registrarAuditoria('LES_DEFENSE', `Emitiu Laudo PDF de Consultoria Defense ${c.id}`, c.id); saveDB(); } function gerarPDFPropostaContratoDefense(contId) { const ct = (db.defense?.contratos || []).find(x => String(x.id) === String(contId)); if (!ct) return; const cli = obterClienteDefensePorId(ct.clienteId) || {}; const diag = (db.defense?.consultorias || []).find(d => String(d.id) === String(ct.diagRef)); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Proposta Comercial LES Defense ${ct.id}</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 15mm 17mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-between; } .sec-title { background: #09090b; color: #ffffff; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.09em; padding: 6px 10px; margin-top: 14px; margin-bottom: 6px; border-radius: 4px; display:flex; justify-content:space-between; } .grid-table { width: 100%; border-collapse: collapse; font-size: 11px; } .grid-table td { border: 1px solid #d4d4d8; padding: 8px 10px; vertical-align: top; line-height: 1.5; } .lbl { display: block; font-size: 8.5px; font-weight: 800; text-transform: uppercase; color: #71717a; margin-bottom: 3px; } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>LES DEFENSE</b> • Proposta Comercial de Consultoria & Monitoramento Anti-Drone (${ct.id})</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir Proposta</button> </div> <div class="page"> <div> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:14px;"> ${obterLogoHTMLDocumento('SUBSIDIÁRIA LES DEFENSE • CONSULTORIA & MONITORAMENTO DE ESPAÇO AÉREO')} <div style="text-align:right;"> <div style="font-size:14px; font-weight:900; text-transform:uppercase;">PROPOSTA EXECUTIVA DE SERVIÇOS & MONITORAMENTO</div> <div style="font-size:11px; font-family:monospace; font-weight:800; margin-top:5px; background:#f4f4f5; padding:3px 8px; border-radius:4px; border:1px solid #d4d4d8; display:inline-block;"> PROPOSTA Nº ${ct.id} | DATA: ${fmtDataBR(ct.data)} | VIGÊNCIA: ${ct.vigencia} </div> </div> </div> <div class="sec-title"><span>01 DADOS DO CONTRATANTE CORPORATIVO</span><span>B2B DEFENSE</span></div> <table class="grid-table"> <tr> <td><span class="lbl">EMPRESA / RAZÃO SOCIAL</span><b>${cli.nome || '-'} (${cli.cnpj || '-'})</b></td> <td><span class="lbl">CONTATO / RESPONSÁVEL</span><b>${cli.contato || '-'} • ${cli.email || ''} • ${cli.whatsapp || ''}</b></td> </tr> <tr> <td><span class="lbl">ENDEREÇO / PLANTA DE ATENDIMENTO</span><b>${diag ? diag.planta : (cli.endereco || '-')}</b></td> <td><span class="lbl">DIAGNÓSTICO TÉCNICO VINCULADO</span><b>${ct.diagRef || 'Consultoria Inicial Inclusa'}</b></td> </tr> </table> <div class="sec-title"><span>02 MODALIDADE DE SERVIÇO E ESCOPO TÉCNICO DE DETECÇÃO</span><span>ESCOPO LES DEFENSE</span></div> <table class="grid-table"> <tr> <td><span class="lbl">MODALIDADE CONTRATADA</span><b style="font-size:12px; color:#09090b;">${ct.modalidade}</b></td> </tr> <tr> <td><span class="lbl">DETALHAMENTO DO ESCOPO E ENTREGÁVEIS</span>${ct.escopo}</td> </tr> ${diag ? ` <tr> <td><span class="lbl">ARQUITETURA DE DETECÇÃO DIMENSIONADA NO SITE SURVEY</span><b>${diag.arquitetura}</b> (${diag.raio})</td> </tr> ` : ''} </table> <div class="sec-title"><span>03 INVESTIMENTO, FATURAMENTO E TERMO DE ACEITE</span><span>CONDIÇÕES COMERCIAIS</span></div> <table class="grid-table"> <tr> <td><span class="lbl">MODELO DE FATURAMENTO</span><b>${ct.periodicidade}</b></td> <td><span class="lbl">PRAZO / VIGÊNCIA DO SERVIÇO</span><b>${ct.vigencia}</b></td> <td style="background:#09090b; color:#fff;"><span class="lbl" style="color:#a1a1aa;">VALOR TOTAL / INVESTIMENTO</span><b style="font-size:15px; color:#34d399;">${fmtBRL(ct.valor)}</b></td> </tr> <tr> <td colspan="3"> <span class="lbl">DADOS DO PRESTADOR E CONFORMIDADE TÉCNICA</span> <b>ALOJA BRASIL COMERCIAL LTDA (LES AVIATION / LES DEFENSE) • CNPJ 48.657.151/0001-43</b><br> Serviço prestado com foco exclusivo em consultoria especializada, inteligência de espaço aéreo e monitoramento por sensores passivos de detecção de drones e localização de telemetria de controle remoto. </td> </tr> </table> </div> <div style="margin-top:24px;"> <div style="display:grid; grid-template-columns:1fr 1fr; gap:32px; margin-bottom:14px; font-size:11px;"> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>LES DEFENSE / DIRETORIA COMERCIAL</b><br>Emitido por: ${ct.vendedor || ct.criadoPor} </div> <div style="border-top:1.5px solid #09090b; padding-top:6px;"> <b>DE ACORDO / ACEITE DO CLIENTE</b><br>${cli.nome || 'Representante Legal'} — Data: ___/___/______ </div> </div> <div style="border-top:2px solid #09090b; padding-top:8px; display:flex; justify-content:space-between; font-size:9.5px; color:#52525b; font-weight:600;"> <span>LES DEFENSE • PROPOSTA EXECUTIVA DE SERVIÇOS E MONITORAMENTO</span> <span>Ref.: ${ct.id}</span> </div> </div> </div> </body> </html> `); win.document.close(); registrarAuditoria('LES_DEFENSE', `Emitiu PDF da Proposta/Contrato Defense ${ct.id}`, ct.id); saveDB(); } // ========================================================= // MÓDULO DE DESENVOLVIMENTO (P&D): TAREFAS, SUBTAREFAS COM JANELA PRÓPRIA, VALOR ESTIMADO, CAIXA DE TEXTO, ANEXO OPCIONAL & VISIBILIDADE POR RESPONSÁVEL PRINCIPAL/SECUNDÁRIO (v1.24) // ========================================================= let filtroStatusDevAtual = 'TODAS'; let subtarefaModalAtiva = { tarefaId: null, subId: null }; const ETAPAS_DEV_LISTA = [ '1. Pesquisa & Definição de Escopo', '2. Escolha & Homologação de Equipamentos', '3. Arquitetura, Suportes & Integração', '4. Testes de Campo & Validação Operacional' ]; function popularSelectsResponsaveisDev() { const selPrin = document.getElementById('dev-tar-resp'); const selSec = document.getElementById('dev-tar-resp-sec'); const nomesUsuarios = USUARIOS_AUTORIZADOS.map(u => u.nome); if (selPrin) { const valAnt = selPrin.value; selPrin.innerHTML = nomesUsuarios.map(n => `<option value="${n}">${n}</option>`).join(''); if (valAnt && nomesUsuarios.includes(valAnt)) { selPrin.value = valAnt; } else if (sessaoAtual.nome && nomesUsuarios.includes(sessaoAtual.nome)) { selPrin.value = sessaoAtual.nome; } } if (selSec) { const valAntSec = selSec.value; selSec.innerHTML = `<option value="">-- Sem segundo responsável (Opcional) --</option>` + nomesUsuarios.map(n => `<option value="${n}">${n}</option>`).join(''); if (valAntSec && nomesUsuarios.includes(valAntSec)) { selSec.value = valAntSec; } } } function usuarioEstaEnvolvidoNaTarefaDev(t, nomeUsuario) { if (!t || !nomeUsuario) return false; const uNorm = normalizarTexto(nomeUsuario); const rPrin = normalizarTexto(t.responsavelPrincipal || ''); const rSec = normalizarTexto(t.responsavelSecundario || ''); const rLegado = normalizarTexto(t.responsavel || ''); if (rPrin && rPrin === uNorm) return true; if (rSec && rSec === uNorm) return true; if (!rPrin && rLegado && (rLegado === uNorm || rLegado.includes(uNorm))) return true; return false; } function calcularProgressoTarefaDev(t) { if (!t) return 0; const subs = Array.isArray(t.subtarefas) ? t.subtarefas : []; if (subs.length > 0) { const feitas = subs.filter(s => s.concluida).length; return Math.round((feitas / subs.length) * 100); } return t.concluida ? 100 : 0; } function calcularValorEstimadoTotalTarefaDev(t) { if (!t || !Array.isArray(t.subtarefas)) return 0; return t.subtarefas.reduce((acc, s) => acc + Number(s.valorEstimado || 0), 0); } function tarefaEstaConcluidaDev(t) { const subs = Array.isArray(t.subtarefas) ? t.subtarefas : []; if (subs.length > 0) { return subs.every(s => s.concluida); } return !!t.concluida; } function adicionarLinhaSubtarefaForm(titulo = '', concluida = false, subId = '', valorEstimado = 0, relatorioTexto = '', anexos = []) { const container = document.getElementById('dev-form-subtarefas-container'); if (!container) return; const row = document.createElement('div'); row.className = 'grid grid-cols-1 md:grid-cols-12 gap-2 items-center bg-white p-2.5 rounded-lg border border-zinc-200 dev-sub-row'; const idGerado = subId || ('SUB-' + Date.now() + '-' + Math.floor(Math.random() * 999)); row.dataset.subId = idGerado; row.dataset.relatorioTexto = relatorioTexto || ''; row.dataset.anexosJson = JSON.stringify(Array.isArray(anexos) ? anexos : []); row.innerHTML = ` <div class="md:col-span-6 flex items-center gap-2"> <input type="checkbox" ${concluida ? 'checked' : ''} class="w-4 h-4 accent-emerald-600 rounded cursor-pointer dev-sub-check" title="Marcar subtarefa como feita"> <input type="text" value="${(titulo || '').replace(/"/g, '"')}" placeholder="Título da subtarefa / etapa..." class="flex-1 border border-zinc-200 rounded-lg px-2.5 py-1.5 text-xs font-semibold dev-sub-titulo"> </div> <div class="md:col-span-3 flex items-center gap-1.5"> <span class="text-[10px] font-bold text-emerald-800 shrink-0">Valor Est. (R$):</span> <input type="number" step="0.01" min="0" value="${Number(valorEstimado || 0)}" placeholder="0.00" class="w-full border border-emerald-300 rounded-lg px-2 py-1.5 text-xs font-mono font-bold text-emerald-950 bg-emerald-50/30 dev-sub-valor"> </div> <div class="md:col-span-3 flex items-center justify-end gap-1.5"> <input type="text" value="${(relatorioTexto || '').replace(/"/g, '"')}" placeholder="Caixa de texto / obs (opcional)" class="flex-1 border border-zinc-200 rounded-lg px-2 py-1.5 text-[11px] dev-sub-texto"> <button type="button" onclick="this.closest('.dev-sub-row').remove()" class="px-2.5 py-1.5 bg-rose-100 hover:bg-rose-200 text-rose-700 rounded-lg font-bold text-xs shrink-0" title="Remover subtarefa">×</button> </div> `; container.appendChild(row); } function abrirNovaTarefaDevRapida() { cancelarEdicaoTarefaDev(); popularSelectsResponsaveisDev(); const prazoDefault = document.getElementById('dev-tar-prazo'); if (prazoDefault && !prazoDefault.value) { const dt = new Date(); dt.setDate(dt.getDate() + 5); prazoDefault.value = dt.toISOString().slice(0, 10); } document.getElementById('card-form-dev-tarefa')?.scrollIntoView({ behavior: 'smooth' }); document.getElementById('dev-tar-titulo')?.focus(); } function salvarTarefaDev(e) { e.preventDefault(); garantirEstruturaDB(); const editId = document.getElementById('dev-tar-edit-id').value.trim(); const titulo = document.getElementById('dev-tar-titulo').value.trim(); const etapa = document.getElementById('dev-tar-etapa').value; const projeto = document.getElementById('dev-tar-projeto').value; const responsavelPrincipal = document.getElementById('dev-tar-resp')?.value || sessaoAtual.nome || 'Matheus Azevedo'; const responsavelSecundario = document.getElementById('dev-tar-resp-sec')?.value || ''; const responsavelFormatado = responsavelSecundario && responsavelSecundario !== responsavelPrincipal ? `${responsavelPrincipal} & ${responsavelSecundario}` : responsavelPrincipal; const prazo = document.getElementById('dev-tar-prazo').value || new Date().toISOString().slice(0, 10); const prioridade = document.getElementById('dev-tar-prioridade').value; const notas = document.getElementById('dev-tar-notas').value.trim(); const agora = carimboHoraAtual(); const rows = Array.from(document.querySelectorAll('#dev-form-subtarefas-container .dev-sub-row')); const subtarefas = rows.map(r => { let anexosAtuais = []; try { anexosAtuais = JSON.parse(r.dataset.anexosJson || '[]'); } catch (err) { anexosAtuais = []; } return { id: r.dataset.subId || ('SUB-' + Date.now() + '-' + Math.floor(Math.random() * 999)), titulo: r.querySelector('.dev-sub-titulo')?.value.trim() || '', concluida: !!r.querySelector('.dev-sub-check')?.checked, valorEstimado: parseFloat(r.querySelector('.dev-sub-valor')?.value) || 0, relatorioTexto: r.querySelector('.dev-sub-texto')?.value.trim() || r.dataset.relatorioTexto || '', anexos: anexosAtuais }; }).filter(s => s.titulo.length > 0); const concluidaCalc = subtarefas.length > 0 ? subtarefas.every(s => s.concluida) : false; if (editId) { const idx = db.desenvolvimento.tarefas.findIndex(t => String(t.id) === String(editId)); if (idx >= 0) { const ant = db.desenvolvimento.tarefas[idx]; db.desenvolvimento.tarefas[idx] = { ...ant, titulo, etapa, projeto, responsavelPrincipal, responsavelSecundario, responsavel: responsavelFormatado, prazo, prioridade, notas, subtarefas, concluida: subtarefas.length > 0 ? concluidaCalc : !!ant.concluida, modificadoPor: sessaoAtual.nome, modificadoEm: agora }; registrarAuditoria('DESENVOLVIMENTO', `Editou Tarefa P&D "${titulo}" (${editId} • Resp.: ${responsavelFormatado})`, editId); cancelarEdicaoTarefaDev(); saveDB(); return; } } const pNorm = normalizarTexto(projeto); const escopoTarefa = usuarioEhExclusivoDefense() ? 'DEFENSE' : usuarioEhExclusivoDrones() ? 'DRONES' : (pNorm.includes('defense') || pNorm.includes('anti-drone')) ? 'DEFENSE' : 'DRONES'; const novoId = 'DEV-' + Date.now().toString().slice(-4); const novaTarefa = { id: novoId, titulo, etapa, projeto, escopo: escopoTarefa, responsavelPrincipal, responsavelSecundario, responsavel: responsavelFormatado, prazo, prioridade, notas, concluida: concluidaCalc, subtarefas, criadoPor: sessaoAtual.nome || 'Sistema', criadoEm: agora }; db.desenvolvimento.tarefas.unshift(novaTarefa); registrarAuditoria('DESENVOLVIMENTO', `Criou Tarefa P&D "${titulo}" (${novoId} • Resp.: ${responsavelFormatado})`, novoId); cancelarEdicaoTarefaDev(); saveDB(); // Se criou subtarefa(s), abre automaticamente a janela da primeira subtarefa criada para o usuário visualizar/preencher se quiser if (subtarefas.length > 0) { abrirJanelaSubtarefaDev(novoId, subtarefas[0].id); } } function editarTarefaDev(id) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(id)); if (!t) return; popularSelectsResponsaveisDev(); document.getElementById('dev-tar-edit-id').value = t.id; document.getElementById('dev-tar-titulo').value = t.titulo || ''; document.getElementById('dev-tar-etapa').value = t.etapa || '1. Pesquisa & Definição de Escopo'; document.getElementById('dev-tar-projeto').value = t.projeto || 'LES Defense — Projeto Anti-Drone'; const selPrin = document.getElementById('dev-tar-resp'); const selSec = document.getElementById('dev-tar-resp-sec'); if (selPrin) selPrin.value = t.responsavelPrincipal || t.responsavel || sessaoAtual.nome || 'Matheus Azevedo'; if (selSec) selSec.value = t.responsavelSecundario || ''; document.getElementById('dev-tar-prazo').value = t.prazo || ''; document.getElementById('dev-tar-prioridade').value = t.prioridade || 'Média'; document.getElementById('dev-tar-notas').value = t.notas || ''; const container = document.getElementById('dev-form-subtarefas-container'); if (container) { container.innerHTML = ''; (t.subtarefas || []).forEach(s => adicionarLinhaSubtarefaForm( s.titulo, s.concluida, s.id, s.valorEstimado || 0, s.relatorioTexto || '', s.anexos || [] )); if ((t.subtarefas || []).length === 0) { adicionarLinhaSubtarefaForm('', false); } } document.getElementById('dev-form-mode-badge').textContent = `MODO DE EDIÇÃO ATIVO • ${t.id}`; document.getElementById('dev-form-titulo').textContent = `Editando Tarefa & Subtarefas: ${t.titulo}`; document.getElementById('btn-submit-dev-tarefa').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Alterações (${t.id})`; document.getElementById('btn-cancelar-edicao-dev').classList.remove('hidden'); document.getElementById('card-form-dev-tarefa')?.scrollIntoView({ behavior: 'smooth' }); } function cancelarEdicaoTarefaDev() { const form = document.getElementById('form-dev-tarefa'); if (form) form.reset(); document.getElementById('dev-tar-edit-id').value = ''; popularSelectsResponsaveisDev(); document.getElementById('dev-form-mode-badge').textContent = 'Cadastro & Edição Completa • Tarefas e Subtarefas'; document.getElementById('dev-form-titulo').textContent = 'Criar / Editar Tarefa de Desenvolvimento e Subtarefas'; document.getElementById('btn-submit-dev-tarefa').innerHTML = `<i class="fa-solid fa-floppy-disk mr-1.5 text-emerald-400"></i> Salvar Tarefa & Subtarefas`; document.getElementById('btn-cancelar-edicao-dev').classList.add('hidden'); const container = document.getElementById('dev-form-subtarefas-container'); if (container) { container.innerHTML = ''; adicionarLinhaSubtarefaForm('', false); } } // ========================================================= // JANELA MODAL EXCLUSIVA DE CADA SUBTAREFA DE P&D (v1.24) // ========================================================= function abrirJanelaSubtarefaDev(tarefaId, subId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); if (!t || !Array.isArray(t.subtarefas)) return; const sub = t.subtarefas.find(s => String(s.id) === String(subId)); if (!sub) return; subtarefaModalAtiva = { tarefaId: String(t.id), subId: String(sub.id) }; document.getElementById('modal-subdev-tarefa-id').value = t.id; document.getElementById('modal-subdev-sub-id').value = sub.id; document.getElementById('modal-subdev-task-badge').textContent = `${t.id} • ${t.titulo}`; document.getElementById('modal-subdev-etapa-badge').textContent = t.etapa || 'Etapa P&D'; const respPrin = t.responsavelPrincipal || t.responsavel || '-'; const respSec = t.responsavelSecundario ? ` | 2º Resp.: ${t.responsavelSecundario}` : ''; document.getElementById('modal-subdev-resp-badge').textContent = `1º Resp.: ${respPrin}${respSec}`; document.getElementById('modal-subdev-header-titulo').textContent = `Subtarefa: ${sub.titulo}`; document.getElementById('modal-subdev-projeto-sub').textContent = `${t.projeto || 'P&D LES'} • Prazo da Tarefa: ${fmtDataBR(t.prazo)}`; document.getElementById('modal-subdev-titulo').value = sub.titulo || ''; document.getElementById('modal-subdev-valor').value = Number(sub.valorEstimado || 0); let campoTextoSub = document.getElementById('modal-subdev-texto'); if (!campoTextoSub) { const mount = document.getElementById('modal-subdev-texto-mount'); if (mount) { campoTextoSub = document.createElement('text' + 'area'); campoTextoSub.id = 'modal-subdev-texto'; campoTextoSub.rows = 4; campoTextoSub.placeholder = 'Descreva aqui o que foi realizado nesta subtarefa, resultados obtidos, parâmetros técnicos, fornecedores ou observações para cumprimento da etapa...'; campoTextoSub.className = 'w-full border border-zinc-300 rounded-xl p-3 text-xs bg-white leading-relaxed focus:outline-none focus:border-zinc-900'; mount.appendChild(campoTextoSub); } } if (campoTextoSub) campoTextoSub.value = sub.relatorioTexto || ''; const chk = document.getElementById('modal-subdev-concluida'); if (chk) { chk.checked = !!sub.concluida; chk.onchange = () => atualizarPillStatusModalSubtarefa(chk.checked); } atualizarPillStatusModalSubtarefa(!!sub.concluida); renderListaAnexosModalSubtarefa(sub); document.getElementById('modal-subtarefa-dev')?.classList.remove('hidden'); } function atualizarPillStatusModalSubtarefa(concluida) { const pill = document.getElementById('modal-subdev-status-pill'); if (!pill) return; if (concluida) { pill.textContent = '✓ Etapa Concluída'; pill.className = 'px-3 py-1 rounded-full text-[10px] font-extrabold uppercase bg-emerald-600 text-white'; } else { pill.textContent = '⏳ Em Andamento'; pill.className = 'px-3 py-1 rounded-full text-[10px] font-extrabold uppercase bg-amber-100 text-amber-900'; } } function fecharJanelaSubtarefaDev() { subtarefaModalAtiva = { tarefaId: null, subId: null }; document.getElementById('modal-subtarefa-dev')?.classList.add('hidden'); } function renderListaAnexosModalSubtarefa(sub) { const listaEl = document.getElementById('modal-subdev-anexos-lista'); if (!listaEl) return; sub.anexos = Array.isArray(sub.anexos) ? sub.anexos : []; if (sub.anexos.length === 0) { listaEl.innerHTML = `<div class="p-3 rounded-lg bg-white border border-dashed border-zinc-300 text-center text-zinc-400 text-[11px] italic">Nenhum documento anexado nesta subtarefa (o anexo é opcional para fechar a etapa).</div>`; return; } listaEl.innerHTML = sub.anexos.map(anx => ` <div class="p-2.5 rounded-lg bg-white border border-zinc-200 flex flex-wrap justify-between items-center gap-2 text-xs"> <div class="flex items-center gap-2 min-w-0"> <i class="fa-solid fa-file-lines text-emerald-600"></i> <div class="truncate"> <b class="text-zinc-900 block truncate">${anx.nome}</b> <span class="text-[10px] text-zinc-400">Anexado por ${anx.enviadoPor || 'Equipe'} em ${anx.dataHora || ''}</span> </div> </div> <div class="flex items-center gap-1.5 shrink-0"> <button type="button" onclick="visualizarAnexoSubtarefaDev('${subtarefaModalAtiva.tarefaId}', '${sub.id}', '${anx.id}')" class="px-2.5 py-1 bg-sky-600 hover:bg-sky-700 text-white rounded text-[10px] font-bold"> <i class="fa-solid fa-eye mr-1"></i>Ver </button> <button type="button" onclick="baixarAnexoSubtarefaDev('${subtarefaModalAtiva.tarefaId}', '${sub.id}', '${anx.id}')" class="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-white rounded text-[10px] font-semibold"> <i class="fa-solid fa-download mr-1"></i>Baixar </button> <button type="button" onclick="removerAnexoSubtarefaDev('${subtarefaModalAtiva.tarefaId}', '${sub.id}', '${anx.id}')" class="px-2 py-1 bg-rose-100 hover:bg-rose-200 text-rose-700 rounded text-[10px] font-bold"> × </button> </div> </div> `).join(''); } function anexarDocumentoNaSubtarefaModal(event) { const files = Array.from(event.target.files || []); if (!files.length || !subtarefaModalAtiva.tarefaId || !subtarefaModalAtiva.subId) return; const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(subtarefaModalAtiva.tarefaId)); if (!t || !Array.isArray(t.subtarefas)) return; const sub = t.subtarefas.find(s => String(s.id) === String(subtarefaModalAtiva.subId)); if (!sub) return; sub.anexos = Array.isArray(sub.anexos) ? sub.anexos : []; let processados = 0; files.forEach(arq => { const r = new FileReader(); r.onload = (ev) => { const dataUrl = ev.target.result; const docId = 'DOC-' + Date.now() + '-' + Math.floor(Math.random() * 999); salvarArquivoNoIndexedDB(`SUBDEV_DOC_${sub.id}_${docId}`, dataUrl); sub.anexos.push({ id: docId, nome: arq.name, tipo: arq.type || 'application/octet-stream', url: dataUrl, enviadoPor: sessaoAtual.nome || 'Equipe P&D', dataHora: carimboHoraAtual() }); processados++; if (processados === files.length) { t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); registrarAuditoria('DESENVOLVIMENTO', `Anexou ${files.length} documento(s) na subtarefa "${sub.titulo}" (${t.id})`, t.id); saveDB(); renderListaAnexosModalSubtarefa(sub); } }; r.readAsDataURL(arq); }); event.target.value = ''; } function obterUrlAnexoSubtarefa(sub, anx) { if (!anx) return ''; if (anx.url && anx.url !== 'IDB_STORED' && anx.url.startsWith('data:')) return anx.url; const ch = `SUBDEV_DOC_${sub.id}_${anx.id}`; return cacheArquivosMemoria[ch] || ''; } function visualizarAnexoSubtarefaDev(tarefaId, subId, docId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); const sub = (t?.subtarefas || []).find(s => String(s.id) === String(subId)); const anx = (sub?.anexos || []).find(a => String(a.id) === String(docId)); if (!sub || !anx) return; const dataUrl = obterUrlAnexoSubtarefa(sub, anx); if (!dataUrl) { alert('Arquivo disponível apenas localmente no dispositivo de envio.'); return; } const blobUrl = converterDataUrlParaBlobUrl(dataUrl); if (blobUrl) { window.open(blobUrl, '_blank'); } } function baixarAnexoSubtarefaDev(tarefaId, subId, docId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); const sub = (t?.subtarefas || []).find(s => String(s.id) === String(subId)); const anx = (sub?.anexos || []).find(a => String(a.id) === String(docId)); if (!sub || !anx) return; const dataUrl = obterUrlAnexoSubtarefa(sub, anx); if (!dataUrl) return; const blobUrl = converterDataUrlParaBlobUrl(dataUrl) || dataUrl; const a = document.createElement('a'); a.href = blobUrl; a.download = anx.nome || 'Documento_Subtarefa_PD'; document.body.appendChild(a); a.click(); a.remove(); } function removerAnexoSubtarefaDev(tarefaId, subId, docId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); const sub = (t?.subtarefas || []).find(s => String(s.id) === String(subId)); if (!sub || !Array.isArray(sub.anexos)) return; sub.anexos = sub.anexos.filter(a => String(a.id) !== String(docId)); t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); saveDB(); renderListaAnexosModalSubtarefa(sub); } function salvarDadosJanelaSubtarefaDev(forcarConclusao = false) { if (!subtarefaModalAtiva.tarefaId || !subtarefaModalAtiva.subId) return; const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(subtarefaModalAtiva.tarefaId)); if (!t || !Array.isArray(t.subtarefas)) return; const sub = t.subtarefas.find(s => String(s.id) === String(subtarefaModalAtiva.subId)); if (!sub) return; const novoTitulo = document.getElementById('modal-subdev-titulo')?.value.trim(); if (!novoTitulo) { alert('Informe o título da subtarefa.'); return; } sub.titulo = novoTitulo; sub.valorEstimado = parseFloat(document.getElementById('modal-subdev-valor')?.value) || 0; sub.relatorioTexto = document.getElementById('modal-subdev-texto')?.value.trim() || ''; sub.concluida = forcarConclusao ? true : !!document.getElementById('modal-subdev-concluida')?.checked; t.concluida = t.subtarefas.length > 0 && t.subtarefas.every(s => s.concluida); t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); registrarAuditoria('DESENVOLVIMENTO', `Atualizou subtarefa "${sub.titulo}" (Valor Est.: ${fmtBRL(sub.valorEstimado)} • ${sub.concluida ? 'Concluída' : 'Em andamento'}) em ${t.id}`, t.id); fecharJanelaSubtarefaDev(); saveDB(); } function toggleSubtarefaDev(tarefaId, subId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); if (!t || !Array.isArray(t.subtarefas)) return; const sub = t.subtarefas.find(s => String(s.id) === String(subId)); if (!sub) return; sub.concluida = !sub.concluida; t.concluida = t.subtarefas.length > 0 && t.subtarefas.every(s => s.concluida); t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); registrarAuditoria('DESENVOLVIMENTO', `${sub.concluida ? 'Concluiu' : 'Reabriu'} subtarefa "${sub.titulo}" em ${t.id}`, t.id); saveDB(); } function toggleTarefaConcluidaDev(tarefaId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); if (!t) return; const novoEstado = !tarefaEstaConcluidaDev(t); t.concluida = novoEstado; if (Array.isArray(t.subtarefas) && t.subtarefas.length > 0) { t.subtarefas.forEach(s => { s.concluida = novoEstado; }); } t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); registrarAuditoria('DESENVOLVIMENTO', `${novoEstado ? 'Concluiu 100%' : 'Reabriu'} tarefa "${t.titulo}" (${t.id})`, t.id); saveDB(); } function adicionarSubtarefaRapidaCard(e, tarefaId) { e.preventDefault(); const input = document.getElementById(`input-nova-sub-${tarefaId}`); const inputVal = document.getElementById(`input-nova-sub-valor-${tarefaId}`); if (!input) return; const texto = input.value.trim(); if (!texto) return; const valorEst = parseFloat(inputVal?.value) || 0; const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); if (!t) return; t.subtarefas = Array.isArray(t.subtarefas) ? t.subtarefas : []; const novoSubId = 'SUB-' + Date.now().toString().slice(-5); t.subtarefas.push({ id: novoSubId, titulo: texto, concluida: false, valorEstimado: valorEst, relatorioTexto: '', anexos: [] }); t.concluida = false; t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); input.value = ''; if (inputVal) inputVal.value = ''; registrarAuditoria('DESENVOLVIMENTO', `Adicionou subtarefa "${texto}" (Valor Est.: ${fmtBRL(valorEst)}) na tarefa ${t.id}`, t.id); saveDB(); // Abre automaticamente a janela da subtarefa criada! abrirJanelaSubtarefaDev(t.id, novoSubId); } function removerSubtarefaDireto(tarefaId, subId) { const t = (db.desenvolvimento?.tarefas || []).find(x => String(x.id) === String(tarefaId)); if (!t || !Array.isArray(t.subtarefas)) return; t.subtarefas = t.subtarefas.filter(s => String(s.id) !== String(subId)); t.concluida = t.subtarefas.length > 0 ? t.subtarefas.every(s => s.concluida) : t.concluida; t.modificadoPor = sessaoAtual.nome; t.modificadoEm = carimboHoraAtual(); saveDB(); } function removerTarefaDev(tarefaId) { db.registrosExcluidos = db.registrosExcluidos || []; db.registrosExcluidos.push(`id:${tarefaId}`); db.desenvolvimento.tarefas = (db.desenvolvimento.tarefas || []).filter(t => String(t.id) !== String(tarefaId)); registrarAuditoria('DESENVOLVIMENTO', `Removeu tarefa de desenvolvimento ${tarefaId}`, tarefaId); saveDB(); } function setFiltroStatusDev(novoFiltro) { filtroStatusDevAtual = novoFiltro; ['TODAS', 'PENDENTES', 'PROXIMAS', 'CONCLUIDAS'].forEach(f => { const btn = document.getElementById('btn-fdev-' + f); if (btn) { if (f === novoFiltro) { btn.className = 'px-3 py-1.5 rounded-lg text-xs font-extrabold bg-zinc-950 text-white shadow-sm'; } else { btn.className = 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-zinc-100 text-zinc-700 hover:bg-zinc-200'; } } }); renderDesenvolvimento(); } function obterBadgePrazoDev(prazoISO, concluida) { if (concluida) { return `<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-emerald-100 text-emerald-900"><i class="fa-solid fa-check mr-1"></i>Concluída (${fmtDataBR(prazoISO)})</span>`; } const dias = diasRestantes(prazoISO); if (dias < 0) { return `<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-rose-600 text-white animate-pulse"><i class="fa-solid fa-triangle-exclamation mr-1"></i>Atrasada há ${Math.abs(dias)}d (${fmtDataBR(prazoISO)})</span>`; } if (dias === 0) { return `<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-500 text-zinc-950"><i class="fa-solid fa-bell mr-1"></i>Vence HOJE (${fmtDataBR(prazoISO)})</span>`; } if (dias <= 7) { return `<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-100 text-amber-900 border border-amber-300"><i class="fa-solid fa-hourglass-half mr-1"></i>Vence em ${dias}d (${fmtDataBR(prazoISO)})</span>`; } return `<span class="px-2.5 py-0.5 rounded-full text-[10px] font-semibold bg-sky-50 text-sky-900 border border-sky-200"><i class="fa-regular fa-calendar mr-1"></i>Prazo: ${fmtDataBR(prazoISO)} (${dias}d)</span>`; } function obterTarefasDesenvolvimentoVisiveis() { const todas = db.desenvolvimento?.tarefas || []; if (!sessaoAtual.nome) return []; // Na parte de desenvolvimento, o projeto/tarefa só aparece para quem for selecionado como Responsável Principal ou Secundário return todas.filter(t => usuarioEstaEnvolvidoNaTarefaDev(t, sessaoAtual.nome)); } function atualizarOpcoesProjetoDesenvolvimentoPorEscopo() { const selProj = document.getElementById('dev-tar-projeto'); if (!selProj) return; const valAnt = selProj.value; let opcoes = []; if (usuarioEhExclusivoDefense()) { opcoes = [ 'LES Defense — Monitoramento & Sensores', 'LES Defense — Arquitetura & Engenharia', 'LES Defense — P&D & Operações' ]; } else if (usuarioEhExclusivoDrones()) { opcoes = [ 'LES Drones — Integração & Sistemas de Recuperação', 'LES Drones — Engenharia & MRO', 'LES Drones — P&D & Homologação' ]; } else { opcoes = [ 'LES Drones — Integração & Sistemas de Recuperação', 'LES Drones — Engenharia & MRO', 'LES Defense — Monitoramento & Sensores', 'LES Defense — Arquitetura & Engenharia', 'Novos Produtos & P&D Holding LES' ]; } selProj.innerHTML = opcoes.map(op => `<option value="${op}">${op}</option>`).join(''); if (valAnt && opcoes.includes(valAnt)) selProj.value = valAnt; } function renderDesenvolvimento() { garantirEstruturaDB(); atualizarOpcoesProjetoDesenvolvimentoPorEscopo(); popularSelectsResponsaveisDev(); const tarefas = obterTarefasDesenvolvimentoVisiveis(); if (document.querySelectorAll('#dev-form-subtarefas-container .dev-sub-row').length === 0) { adicionarLinhaSubtarefaForm('', false); } let totalUnidades = 0; let concluidasUnidades = 0; let totalSubtarefas = 0; let concluidasSubtarefas = 0; let orcamentoEstimadoGlobal = 0; tarefas.forEach(t => { const subs = Array.isArray(t.subtarefas) ? t.subtarefas : []; orcamentoEstimadoGlobal += calcularValorEstimadoTotalTarefaDev(t); if (subs.length > 0) { totalUnidades += subs.length; const feitas = subs.filter(s => s.concluida).length; concluidasUnidades += feitas; totalSubtarefas += subs.length; concluidasSubtarefas += feitas; } else { totalUnidades += 1; if (t.concluida) concluidasUnidades += 1; } }); const percGlobal = totalUnidades > 0 ? Math.round((concluidasUnidades / totalUnidades) * 100) : 0; const qtdTarefasConcluidas = tarefas.filter(t => tarefaEstaConcluidaDev(t)).length; const qtdTarefasPendentes = tarefas.length - qtdTarefasConcluidas; const pendentesLista = tarefas .filter(t => !tarefaEstaConcluidaDev(t)) .slice() .sort((a, b) => diasRestantes(a.prazo) - diasRestantes(b.prazo)); const qtdProximas7Dias = pendentesLista.filter(t => { const d = diasRestantes(t.prazo); return d >= 0 && d <= 7; }).length; const qtdAtrasadas = pendentesLista.filter(t => diasRestantes(t.prazo) < 0).length; const elPerc = document.getElementById('dev-panorama-percentual'); const elBarra = document.getElementById('dev-panorama-barra'); const elSub = document.getElementById('dev-panorama-subtitulo'); if (elPerc) elPerc.textContent = `${percGlobal}%`; if (elBarra) elBarra.style.width = `${percGlobal}%`; if (elSub) { elSub.textContent = `Projetos onde ${sessaoAtual.nome || 'você'} está envolvido(a): ${qtdTarefasConcluidas}/${tarefas.length} tarefas concluídas • ${concluidasSubtarefas}/${totalSubtarefas} subtarefas • Valor Estimado Total: ${fmtBRL(orcamentoEstimadoGlobal)}`; } const gridEtapas = document.getElementById('dev-etapas-progresso-grid'); if (gridEtapas) { gridEtapas.innerHTML = ETAPAS_DEV_LISTA.map(etapaNome => { const daEtapa = tarefas.filter(t => t.etapa === etapaNome); const mediaEtapa = daEtapa.length > 0 ? Math.round(daEtapa.reduce((acc, t) => acc + calcularProgressoTarefaDev(t), 0) / daEtapa.length) : 0; const somaEtapa = daEtapa.reduce((acc, t) => acc + calcularValorEstimadoTotalTarefaDev(t), 0); return ` <div class="p-3 rounded-xl bg-zinc-50 border border-zinc-200 space-y-1.5"> <div class="flex justify-between items-center text-[11px]"> <span class="font-bold text-zinc-800 truncate pr-2">${etapaNome}</span> <span class="font-extrabold ${mediaEtapa === 100 ? 'text-emerald-700' : 'text-zinc-900'}">${mediaEtapa}%</span> </div> <div class="w-full bg-zinc-200 rounded-full h-2 overflow-hidden"> <div class="${mediaEtapa === 100 ? 'bg-emerald-600' : 'bg-sky-600'} h-full rounded-full transition-all duration-300" style="width:${mediaEtapa}%"></div> </div> <span class="block text-[10px] text-zinc-500">${daEtapa.length} tarefa(s) • Est.: <b>${fmtBRL(somaEtapa)}</b></span> </div> `; }).join(''); } const kpisEl = document.getElementById('dev-kpis-grid'); if (kpisEl) { kpisEl.innerHTML = ` <div class="les-card p-4 border-l-4 border-l-emerald-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Progresso Geral</p> <p class="text-2xl font-extrabold text-emerald-700 mt-1">${percGlobal}%</p> <p class="text-[11px] text-zinc-400">${concluidasSubtarefas}/${totalSubtarefas} subtarefas feitas</p> </div> <div class="les-card p-4 border-l-4 border-l-zinc-950"> <p class="text-[10px] font-bold uppercase text-zinc-500">Minhas Tarefas P&D</p> <p class="text-2xl font-extrabold text-zinc-950 mt-1">${qtdTarefasConcluidas} / ${tarefas.length}</p> <p class="text-[11px] text-zinc-400">Selecionadas p/ ${sessaoAtual.nome || 'usuário'}</p> </div> <div class="les-card p-4 border-l-4 border-l-sky-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Valor Estimado P&D</p> <p class="text-xl font-extrabold text-sky-700 mt-1 font-mono">${fmtBRL(orcamentoEstimadoGlobal)}</p> <p class="text-[11px] text-zinc-400">Soma das etapas/subtarefas</p> </div> <div class="les-card p-4 border-l-4 border-l-amber-500"> <p class="text-[10px] font-bold uppercase text-zinc-500">Prazos Próximos (≤ 7d)</p> <p class="text-2xl font-extrabold text-amber-600 mt-1">${qtdProximas7Dias}</p> <p class="text-[11px] text-zinc-400">Vencendo nos próximos dias</p> </div> <div class="les-card p-4 border-l-4 border-l-rose-600"> <p class="text-[10px] font-bold uppercase text-zinc-500">Prazos Vencidos</p> <p class="text-2xl font-extrabold text-rose-600 mt-1">${qtdAtrasadas}</p> <p class="text-[11px] text-zinc-400">Atenção imediata</p> </div> `; } const badgeUrg = document.getElementById('dev-badge-urgentes'); if (badgeUrg) { badgeUrg.textContent = `${qtdProximas7Dias + qtdAtrasadas} com prazo próximo ou vencido • ${qtdTarefasPendentes} pendente(s)`; } const radarEl = document.getElementById('dev-radar-prazos-lista'); if (radarEl) { if (pendentesLista.length === 0) { radarEl.innerHTML = `<div class="md:col-span-2 p-4 text-center text-emerald-700 bg-emerald-50 rounded-xl border border-emerald-200 text-xs font-bold"><i class="fa-solid fa-circle-check mr-1"></i> Nenhuma tarefa pendente para o seu usuário no momento!</div>`; } else { radarEl.innerHTML = pendentesLista.map(t => { const prog = calcularProgressoTarefaDev(t); const subs = t.subtarefas || []; const subsRestantes = subs.filter(s => !s.concluida).length; const valEstTotal = calcularValorEstimadoTotalTarefaDev(t); return ` <div class="p-3.5 rounded-xl border bg-zinc-50 hover:bg-white transition flex flex-col justify-between gap-2.5 text-xs"> <div class="flex justify-between items-start gap-2"> <div> <span class="text-[10px] font-bold uppercase text-zinc-400 block">${t.etapa} • 1º Resp.: <b class="text-zinc-700">${t.responsavelPrincipal || t.responsavel}</b>${t.responsavelSecundario ? ` | 2º: <b class="text-sky-700">${t.responsavelSecundario}</b>` : ''}</span> <b class="text-zinc-950 text-xs">${t.titulo}</b> </div> <div class="shrink-0">${obterBadgePrazoDev(t.prazo, false)}</div> </div> <div class="flex items-center gap-3"> <div class="flex-1 bg-zinc-200 rounded-full h-2 overflow-hidden"> <div class="bg-amber-500 h-full rounded-full" style="width:${prog}%"></div> </div> <span class="font-extrabold text-[11px] text-zinc-700">${prog}%</span> <span class="text-[10px] text-zinc-500">(${subsRestantes} pendente(s) • Est.: ${fmtBRL(valEstTotal)})</span> </div> <div class="flex justify-end gap-1.5 pt-1 border-t border-zinc-200/70"> <button type="button" onclick="editarTarefaDev('${t.id}')" class="px-2.5 py-1 bg-sky-600 hover:bg-sky-700 text-white rounded-lg text-[10px] font-bold"> <i class="fa-solid fa-pen-to-square mr-1"></i> Editar Tarefa </button> <button type="button" onclick="toggleTarefaConcluidaDev('${t.id}')" class="px-2.5 py-1 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-[10px] font-bold"> <i class="fa-solid fa-check mr-1"></i> Marcar 100% Feito </button> </div> </div> `; }).join(''); } } const etapaFiltro = document.getElementById('filtro-dev-etapa')?.value || ''; const termoBusca = normalizarTexto(document.getElementById('filtro-dev-busca')?.value || ''); const filtradas = tarefas.filter(t => { const conc = tarefaEstaConcluidaDev(t); const dias = diasRestantes(t.prazo); if (filtroStatusDevAtual === 'PENDENTES' && conc) return false; if (filtroStatusDevAtual === 'CONCLUIDAS' && !conc) return false; if (filtroStatusDevAtual === 'PROXIMAS' && (conc || dias > 7)) return false; if (etapaFiltro && t.etapa !== etapaFiltro) return false; if (termoBusca) { const subsTxt = (t.subtarefas || []).map(s => `${s.titulo} ${s.relatorioTexto || ''}`).join(' '); const tudo = normalizarTexto(`${t.id} ${t.titulo} ${t.etapa} ${t.projeto} ${t.responsavelPrincipal || ''} ${t.responsavelSecundario || ''} ${t.responsavel} ${t.notas} ${subsTxt}`); if (!tudo.includes(termoBusca)) return false; } return true; }); const listaEl = document.getElementById('lista-dev-tarefas'); if (listaEl) { if (filtradas.length === 0) { listaEl.innerHTML = `<div class="p-8 text-center text-zinc-400 text-xs italic border rounded-xl bg-zinc-50">Nenhum projeto/tarefa de P&D atribuído a ${sessaoAtual.nome || 'seu usuário'} para este filtro.</div>`; } else { listaEl.innerHTML = filtradas.map(t => { const conc = tarefaEstaConcluidaDev(t); const prog = calcularProgressoTarefaDev(t); const subs = Array.isArray(t.subtarefas) ? t.subtarefas : []; const feitasCount = subs.filter(s => s.concluida).length; const somaEstimadaTarefa = calcularValorEstimadoTotalTarefaDev(t); const respPrin = t.responsavelPrincipal || t.responsavel || '-'; const respSec = t.responsavelSecundario || ''; return ` <div class="p-5 rounded-xl border-2 ${conc ? 'bg-emerald-50/30 border-emerald-300' : 'bg-white border-zinc-200 hover:border-zinc-300'} shadow-sm space-y-4 transition"> <div class="flex flex-wrap justify-between items-start gap-3"> <div class="flex items-start gap-3"> <input type="checkbox" ${conc ? 'checked' : ''} onchange="toggleTarefaConcluidaDev('${t.id}')" class="w-5 h-5 mt-0.5 accent-emerald-600 rounded cursor-pointer" title="Marcar/desmarcar etapa inteira como concluída"> <div> <div class="flex flex-wrap items-center gap-2 mb-1"> <span class="font-mono font-black text-[11px] bg-zinc-950 text-emerald-400 px-2 py-0.5 rounded">${t.id}</span> <span class="px-2.5 py-0.5 rounded-md text-[10px] font-bold bg-zinc-100 text-zinc-800 border border-zinc-200">${t.etapa}</span> <span class="px-2 py-0.5 rounded-md text-[10px] font-semibold bg-sky-50 text-sky-800">${t.projeto}</span> <span class="px-2 py-0.5 rounded-md text-[10px] font-bold ${t.prioridade === 'Alta' ? 'bg-rose-100 text-rose-800' : t.prioridade === 'Média' ? 'bg-amber-100 text-amber-800' : 'bg-zinc-100 text-zinc-600'}">Prioridade ${t.prioridade}</span> <span class="px-2.5 py-0.5 rounded-md text-[10px] font-mono font-extrabold bg-emerald-50 text-emerald-900 border border-emerald-200">Valor Est. Total: ${fmtBRL(somaEstimadaTarefa)}</span> </div> <h4 class="text-sm font-extrabold ${conc ? 'line-through text-zinc-500' : 'text-zinc-950'}">${t.titulo}</h4> ${t.notas ? `<p class="text-xs text-zinc-600 mt-1"><i class="fa-regular fa-note-sticky mr-1 text-zinc-400"></i>${t.notas}</p>` : ''} <div class="flex flex-wrap items-center gap-2 mt-1.5 text-[11px]"> <span class="px-2 py-0.5 rounded bg-emerald-50 border border-emerald-200 text-emerald-900 font-semibold"> <i class="fa-solid fa-user-Secret mr-1 text-emerald-600"></i>1º Responsável (Principal): <b>${respPrin}</b> </span> ${respSec ? ` <span class="px-2 py-0.5 rounded bg-sky-50 border border-sky-200 text-sky-900 font-semibold"> <i class="fa-solid fa-user-plus mr-1 text-sky-600"></i>2º Responsável (Secundário): <b>${respSec}</b> </span> ` : ''} </div> </div> </div> <div class="flex flex-col items-end gap-2"> ${obterBadgePrazoDev(t.prazo, conc)} <div class="flex items-center gap-1.5"> <button type="button" onclick="editarTarefaDev('${t.id}')" class="px-3 py-1.5 bg-sky-600 hover:bg-sky-700 text-white rounded-lg text-xs font-bold shadow-sm"> <i class="fa-solid fa-pen-to-square mr-1"></i> Editar Tarefa </button> <button type="button" onclick="removerTarefaDev('${t.id}')" class="px-2.5 py-1.5 bg-rose-100 hover:bg-rose-200 text-rose-700 rounded-lg text-xs font-semibold"> Excluir </button> </div> </div> </div> <div class="bg-zinc-50 p-3 rounded-xl border border-zinc-200 space-y-1.5"> <div class="flex justify-between items-center text-xs"> <span class="font-bold text-zinc-700">Progresso desta Etapa (${feitasCount} de ${subs.length} subtarefas feitas)</span> <span class="font-black text-sm ${prog === 100 ? 'text-emerald-600' : 'text-sky-700'}">${prog}%</span> </div> <div class="w-full bg-zinc-200 rounded-full h-2.5 overflow-hidden"> <div class="${prog === 100 ? 'bg-emerald-600' : 'bg-sky-600'} h-full rounded-full transition-all duration-300" style="width: ${prog}%"></div> </div> </div> <div class="space-y-2"> <span class="text-[10px] font-extrabold uppercase tracking-wider text-zinc-500 block">Subtarefas Criadas (Clique na subtarefa ou em "Abrir Janela" para acessar a Caixa de Texto, Valor Estimado e Anexação de Documentos):</span> <div class="space-y-1.5"> ${subs.map(s => { const qtdDocs = Array.isArray(s.anexos) ? s.anexos.length : 0; const temTexto = !!(s.relatorioTexto && s.relatorioTexto.trim().length > 0); const valEstSub = Number(s.valorEstimado || 0); return ` <div class="p-2.5 rounded-lg border ${s.concluida ? 'bg-emerald-50/70 border-emerald-200' : 'bg-zinc-50 border-zinc-200 hover:border-zinc-300'} flex flex-wrap items-center justify-between gap-2 text-xs transition"> <div class="flex items-center gap-2.5 flex-1 min-w-[200px]"> <input type="checkbox" ${s.concluida ? 'checked' : ''} onchange="toggleSubtarefaDev('${t.id}', '${s.id}')" class="w-4 h-4 accent-emerald-600 rounded cursor-pointer" title="Concluir/reabrir subtarefa diretamente"> <button type="button" onclick="abrirJanelaSubtarefaDev('${t.id}', '${s.id}')" class="text-left flex-1 hover:text-sky-700 ${s.concluida ? 'line-through text-zinc-500 font-medium' : 'text-zinc-900 font-bold'}"> ${s.titulo} </button> </div> <div class="flex flex-wrap items-center gap-1.5 shrink-0"> <span class="px-2 py-0.5 rounded bg-white border border-emerald-300 text-emerald-900 font-mono text-[10px] font-extrabold" title="Valor Estimado desta Etapa"> Est.: ${fmtBRL(valEstSub)} </span> ${temTexto ? `<span class="px-2 py-0.5 rounded bg-sky-100 text-sky-900 text-[10px] font-bold" title="Caixa de texto preenchida"><i class="fa-solid fa-align-left mr-1"></i>Texto</span>` : ''} ${qtdDocs > 0 ? `<span class="px-2 py-0.5 rounded bg-amber-100 text-amber-900 text-[10px] font-bold" title="Documentos anexados"><i class="fa-solid fa-paperclip mr-1"></i>${qtdDocs} doc(s)</span>` : ''} <button type="button" onclick="abrirJanelaSubtarefaDev('${t.id}', '${s.id}')" class="px-2.5 py-1 bg-zinc-950 hover:bg-zinc-800 text-white rounded-lg text-[10px] font-bold shadow-sm inline-flex items-center gap-1"> <i class="fa-solid fa-up-right-from-square text-emerald-400"></i> Abrir Janela </button> <button type="button" onclick="removerSubtarefaDireto('${t.id}', '${s.id}')" title="Remover subtarefa" class="px-2 py-1 bg-white hover:bg-rose-50 border border-zinc-200 text-rose-600 rounded text-[10px] font-bold"> × </button> </div> </div> `; }).join('')} </div> <form onsubmit="adicionarSubtarefaRapidaCard(event, '${t.id}')" class="grid grid-cols-1 md:grid-cols-12 gap-2 pt-1"> <input type="text" id="input-nova-sub-${t.id}" placeholder="Nova subtarefa (ao adicionar, abre a janela da subtarefa)..." class="md:col-span-6 border border-zinc-300 rounded-lg px-3 py-1.5 text-xs bg-white"> <input type="number" step="0.01" min="0" id="input-nova-sub-valor-${t.id}" placeholder="Valor Estimado (R$)" class="md:col-span-3 border border-emerald-300 rounded-lg px-3 py-1.5 text-xs font-mono bg-white"> <button type="submit" class="md:col-span-3 px-3.5 py-1.5 bg-zinc-900 hover:bg-zinc-800 text-white rounded-lg text-xs font-bold shrink-0"> <i class="fa-solid fa-plus mr-1 text-emerald-400"></i> Criar & Abrir Subtarefa </button> </form> </div> </div> `; }).join(''); } } } function gerarPDFRelatorioDesenvolvimento() { const tarefas = obterTarefasDesenvolvimentoVisiveis(); const nomeEscopoPDF = usuarioEhExclusivoDefense() ? 'LES DEFENSE' : usuarioEhExclusivoDrones() ? 'LES DRONES' : 'LES AVIATION HOLDING'; let totalUnidades = 0; let concluidasUnidades = 0; let somaGlobalEst = 0; tarefas.forEach(t => { somaGlobalEst += calcularValorEstimadoTotalTarefaDev(t); const subs = Array.isArray(t.subtarefas) ? t.subtarefas : []; if (subs.length > 0) { totalUnidades += subs.length; concluidasUnidades += subs.filter(s => s.concluida).length; } else { totalUnidades += 1; if (t.concluida) concluidasUnidades += 1; } }); const percGlobal = totalUnidades > 0 ? Math.round((concluidasUnidades / totalUnidades) * 100) : 0; const blocosHTML = tarefas.map(t => { const prog = calcularProgressoTarefaDev(t); const conc = tarefaEstaConcluidaDev(t); const somaTar = calcularValorEstimadoTotalTarefaDev(t); const subsHTML = (t.subtarefas || []).map(s => `<div style="padding:4px 0; font-size:10.5px; border-bottom:1px dashed #e4e4e7;"> <div style="display:flex; justify-content:space-between;"> <span>${s.concluida ? '☑️ <s>' + s.titulo + '</s>' : '⬜ <b>' + s.titulo + '</b>'}</span> <span style="font-family:monospace; font-weight:700;">Valor Est.: ${fmtBRL(s.valorEstimado || 0)} ${(s.anexos || []).length > 0 ? `• ${(s.anexos || []).length} doc(s)` : ''}</span> </div> ${s.relatorioTexto ? `<div style="font-size:9.5px; color:#52525b; margin-top:2px; padding-left:18px;">Obs/Relatório: ${s.relatorioTexto}</div>` : ''} </div>` ).join(''); return ` <div style="border:1px solid #d4d4d8; border-radius:6px; padding:10px 12px; margin-bottom:10px; background:#fafafa;"> <div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #e4e4e7; padding-bottom:6px; margin-bottom:6px;"> <div> <span style="font-family:monospace; font-weight:800; background:#09090b; color:#fff; padding:2px 6px; border-radius:4px; font-size:10px;">${t.id}</span> <b style="font-size:12px; margin-left:6px;">${t.titulo}</b> <span style="font-size:10px; color:#52525b; display:block; margin-top:2px;">${t.etapa} • Projeto: ${t.projeto} • 1º Resp.: <b>${t.responsavelPrincipal || t.responsavel}</b> ${t.responsavelSecundario ? `| 2º Resp.: <b>${t.responsavelSecundario}</b>` : ''}</span> </div> <div style="text-align:right;"> <span style="font-weight:900; font-size:13px; color:${conc ? '#047857' : '#0369a1'};">${prog}% (${fmtBRL(somaTar)})</span> <span style="display:block; font-size:10px; font-weight:700;">Prazo: ${fmtDataBR(t.prazo)}</span> </div> </div> ${subsHTML} </div> `; }).join(''); const win = window.open('', '_blank'); win.document.write(` <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>Panorama de Desenvolvimento & Tarefas — ${nomeEscopoPDF}</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { font-family: 'Inter', sans-serif; color: #09090b; background: #e4e4e7; margin: 0; padding: 18px; } .page { width: 210mm; min-height: 290mm; background: #ffffff; margin: 0 auto; padding: 14mm 16mm; box-shadow: 0 10px 25px rgba(0,0,0,0.1); } @media print { body { background: #fff; padding: 0; } .page { box-shadow: none; margin: 0; width: 100%; } .no-print { display: none !important; } } </style> </head> <body> <div class="no-print" style="max-width:210mm; margin:0 auto 14px auto; display:flex; justify-content:space-between; align-items:center; background:#09090b; color:#fff; padding:10px 16px; border-radius:8px;"> <span style="font-size:12px;"><b>${nomeEscopoPDF}</b> • Panorama Geral de Progresso do Desenvolvimento (${percGlobal}%)</span> <button onclick="window.print()" style="background:#10b981; color:#fff; border:none; padding:8px 16px; border-radius:6px; font-weight:800; cursor:pointer; font-size:12px;">Salvar PDF / Imprimir</button> </div> <div class="page"> <div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #09090b; padding-bottom:12px; margin-bottom:14px;"> ${obterLogoHTMLDocumento(nomeEscopoPDF + ' • ENGENHARIA, PESQUISA & DESENVOLVIMENTO')} <div style="text-align:right;"> <div style="font-size:14px; font-weight:900; text-transform:uppercase;">RELATÓRIO EXECUTIVO DE DESENVOLVIMENTO & PROGRESSO</div> <div style="font-size:11px; margin-top:4px;">PROGRESSO GLOBAL: <b style="color:#047857; font-size:14px;">${percGlobal}%</b> | VALOR EST.: <b>${fmtBRL(somaGlobalEst)}</b> | EMISSÃO: ${carimboHoraAtual()}</div> </div> </div> ${blocosHTML} </div> </body> </html> `); win.document.close(); } function renderRelatorioMensal() { const mes = document.getElementById('filtro-mes'); if (!mes) return; if (!mes.value) mes.value = '2026-10'; const filtrados = db.logbook.filter(l => l.data && l.data.startsWith(mes.value)); document.getElementById('relatorio-total').textContent = filtrados.length; document.getElementById('relatorio-subtitulo').textContent = 'Competência: ' + mes.value; document.getElementById('tabela-relatorio-mensal').innerHTML = filtrados.map(l => `<tr><td class="p-2">${l.data}</td><td class="p-2 font-mono">${l.sn}</td><td class="p-2 font-bold">${l.tipo}</td><td class="p-2">${l.desc}</td><td class="p-2">${l.criadoPor || l.tecnico}</td></tr>`).join(''); } function carregarLogoArquivo(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = e => { try { localStorage.setItem(LOGO_STORAGE_KEY, e.target.result); } catch (err) {} aplicarLogoCustomizada(); }; reader.readAsDataURL(file); } function aplicarLogoCustomizada() { const saved = localStorage.getItem(LOGO_STORAGE_KEY); if (saved) { document.getElementById('logo-container-sidebar').classList.add('hidden'); const img = document.getElementById('custom-logo-sidebar'); img.src = saved; img.classList.remove('hidden'); } } function exportarJSON() { if (!usuarioEhMasterMatheus()) { alert('Apenas o perfil Master do Matheus possui permissão para exportar a base.'); return; } const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(db, null, 2)); const dl = document.createElement('a'); dl.setAttribute("href", dataStr); dl.setAttribute("download", "les_aviation_backup_v1_23.json"); dl.click(); } function importarBackupJSON(event) { if (!usuarioEhMasterMatheus()) { alert('Apenas o perfil Master do Matheus possui permissão para importar backup.'); return; } const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = e => { try { db = JSON.parse(e.target.result); garantirEstruturaDB(); saveDB(); alert('Base restaurada e sincronizada com a nuvem!'); } catch (err) { alert('JSON inválido.'); } }; reader.readAsText(file); } aplicarLogoCustomizada(); sincronizarComNuvemAgora(false); setInterval(() => { if (sessaoAtual.perfil) sincronizarComNuvemAgora(false); }, 20000); </script> </body> </html>