Operacional Atenção Crítico
Estoque Central BH Reset de Carga TotalConfigurar Metas
Gestão de Fluxo 3D (12%)
Base Inteligente SAC24
Exportar Mapa PDF
Legenda do Mapa
Superávit
Saudável
Atenção
Crítico
Sem Dados
DME Central
window.toggleFullScreen = () => {
const elem = document.documentElement;
const hasFullscreen = elem.requestFullscreen || elem.webkitRequestFullscreen || elem.msRequestFullscreen;
if (!hasFullscreen) {
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
if (isIOS && !window.navigator.standalone) {
alert("⚠️ TELA CHEIA NO IPHONE/IOS:\n\nA Apple bloqueia a função de tela cheia no Safari.\nPara ter a experiência de aplicativo PWA, toque no botão 'Compartilhar' do Safari (ícone com seta para cima) e escolha a opção 'Adicionar à Tela de Início'.");
} else {
window.showToast("Seu navegador não suporta tela cheia nativa.", "error");
}
return;
}
if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {
if (elem.requestFullscreen) { elem.requestFullscreen().catch(err => console.error(err)); }
else if (elem.webkitRequestFullscreen) { elem.webkitRequestFullscreen(); }
else if (elem.msRequestFullscreen) { elem.msRequestFullscreen(); }
} else {
if (document.exitFullscreen) { document.exitFullscreen(); }
else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); }
else if (document.msExitFullscreen) { document.msExitFullscreen(); }
}
};
document.addEventListener('fullscreenchange', () => {
const icon = document.getElementById('fullscreen-icon');
if (icon) { icon.setAttribute('data-lucide', document.fullscreenElement ? 'minimize' : 'maximize'); lucide.createIcons(); }
});
document.addEventListener('webkitfullscreenchange', () => {
const icon = document.getElementById('fullscreen-icon');
if (icon) { icon.setAttribute('data-lucide', document.webkitFullscreenElement ? 'minimize' : 'maximize'); lucide.createIcons(); }
});
const savedTheme = localStorage.getItem('sigme_theme') || 'dark';
window.changeTheme(savedTheme);
const selTheme = $('theme-selector');
if(selTheme) selTheme.value = savedTheme;
window.toggleSidebar = () => { $('main-sidebar').classList.toggle('-translate-x-full'); $('sidebar-overlay').classList.toggle('hidden'); };
window.ensureSpacecomSaidaSelects = function() {
const destinoEl = document.getElementById('sc-saida-destino');
const itemEl = document.getElementById('sc-saida-item');
const itensSaida = ['TZPR', 'UPR', 'CM', 'TRAVAS', 'CINTAS', 'FONTE', 'MANUAL_MONIT', 'MANUAL_VITIMA', 'MANUAL_APP'];
const nomeItem = {
TZPR: 'TORNOZELEIRA',
UPR: 'UNIDADE PORTÁTIL DE RASTREAMENTO',
CM: 'CARREGADOR MAGNÉTICO',
TRAVAS: 'TRAVAS',
CINTAS: 'CINTAS',
FONTE: 'FONTE',
MANUAL_MONIT: 'MANUAL MONITORADO',
MANUAL_VITIMA: 'MANUAL VÍTIMA',
MANUAL_APP: 'MANUAL APP'
};
const unidadesRaw = window.currentUser?.unidades;
let unidades = [];
if (Array.isArray(unidadesRaw)) {
unidades = unidadesRaw;
} else if (typeof unidadesRaw === 'string' && unidadesRaw.trim()) {
unidades = unidadesRaw.split(',').map(s => s.trim()).filter(Boolean);
}
unidades = unidades.filter(u => u && String(u).trim().toLowerCase() !== 'spacecom');
if (unidades.length === 0) unidades = ['Central BH'];
if (destinoEl && destinoEl.options.length === 0) {
destinoEl.innerHTML = '';
[...new Set(unidades)].forEach(u => {
const opt = document.createElement('option');
opt.value = u;
opt.textContent = u;
destinoEl.appendChild(opt);
});
}
if (itemEl && itemEl.options.length === 0) {
itemEl.innerHTML = '';
itensSaida.forEach(i => {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = (typeof window.formatItemName === 'function') ? window.formatItemName(i) : (nomeItem[i] || i);
itemEl.appendChild(opt);
});
}
};
window.switchTab = function(tabId) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-btn-mob').forEach(el => { el.classList.remove('nav-mob-active'); }); $(tabId)?.classList.add('active'); if($(`btn-mob-${tabId}`)) { $(`btn-mob-${tabId}`).classList.add('nav-mob-active'); }
if (tabId === 'dashboard') renderDashboard(); if (tabId === 'nucleos') renderNucleoDashboard(); if (tabId === 'logistica') renderKanban('suprimento'); if (tabId === 'devolucoes') renderKanban('devolucao'); if (tabId === 'utilizados') renderAdmissoes(); if (tabId === 'outros-estados') renderOutrosEstados(); if (tabId === 'relatorio') window.gerarRelatorio(); if (tabId === 'graficos') window.renderGraficosAnalytics(); if (tabId === 'fluxo-estoque') window.renderFluxoEstoque(); if (tabId === 'spacecom-recebimento') { if (typeof window.carregarRecebimentosSpacecom === 'function') window.carregarRecebimentosSpacecom(); } if (tabId === 'spacecom-triagem') { window.ensureSpacecomSaidaSelects(); if (typeof window.carregarRecibosSpacecom === 'function') window.carregarRecibosSpacecom(); if (typeof window.initSpacecomItems === 'function') window.initSpacecomItems(); setTimeout(() => window.ensureSpacecomSaidaSelects(), 120); }
const lgnd = $('legend-component'); if(lgnd) { if((tabId === 'dashboard' || tabId === 'nucleos') && (currentUser.role === 'admin' || currentUser.role === 'central')) { lgnd.classList.remove('hidden'); lgnd.classList.add('flex'); } else { lgnd.classList.add('hidden'); lgnd.classList.remove('flex'); } }
};
function setupWorkspace() {
window.currentUser = currentUser;
let displayRole = currentUser.role === 'admin' ? "ADMINISTRADOR GERAL" : (currentUser.role === 'central' ? "ESTOQUE CENTRAL BH" : (currentUser.role === 'spacecom' ? "COLABORADOR SPACECOM" : "NÚCLEO REGIONAL"));
const nomeHeader = currentUser.role === 'spacecom'
? (currentUser.nomeColaborador || currentUser.nome || currentUser.masp || 'COLABORADOR SPACECOM')
: (currentUser.nome || 'USUÁRIO');
$('header-user-role').innerText = displayRole; $('header-user-nome').innerHTML = `${nomeHeader} `;
const navSidebar = $('nav-menu-sidebar'); navSidebar.innerHTML = '';
const addTab = (id, icon, text) => { navSidebar.innerHTML += ` ${text} `; };
if (currentUser.role === 'spacecom') { addTab('spacecom-recebimento', 'inbox', 'Recebimento'); addTab('spacecom-triagem', 'package-check', 'Gestão de Triagem'); }
else if (currentUser.role !== 'nucleo') { addTab('dashboard', 'database', 'Central BH'); addTab('fluxo-estoque', 'map', 'Fluxo de Estoque'); addTab('nucleos', 'map-pin', 'Interior'); addTab('logistica', 'truck', 'Envios Núcleos/Subnúcleos'); addTab('devolucoes', 'corner-up-left', 'Devoluções'); addTab('utilizados', 'user-check', 'Admissão (Uso)'); addTab('outros-estados', 'globe', 'Outros Estados'); addTab('movimentar', 'edit-3', 'Entrada/Saída Spacecom'); addTab('spacecom-recebimento', 'inbox', 'Recebimento Spacecom'); addTab('spacecom-triagem', 'package-check', 'Gestão de Triagem'); addTab('auditoria', 'scan-line', 'Auditoria Equip. Pagos'); addTab('relatorio', 'file-spreadsheet', 'Relatórios'); addTab('graficos', 'pie-chart', 'Gráficos'); addTab('plantao', 'clipboard-check', 'Passagem de Plantão'); } else { addTab('nucleos', 'map-pin', 'Meu Estoque'); addTab('logistica', 'truck', 'Meus Pedidos'); addTab('devolucoes', 'corner-up-left', 'Devolver Material'); addTab('utilizados', 'user-check', 'Registrar Admissão'); addTab('movimentar', 'edit-3', 'Entrada/Saída Spacecom'); addTab('spacecom-recebimento', 'inbox', 'Recebimento Spacecom'); addTab('spacecom-triagem', 'package-check', 'Gestão de Triagem'); addTab('plantao', 'clipboard-check', 'Passagem de Plantão'); }
addTab('ajuda-ia', 'help-circle', 'SIGME AI / Ajuda');
if(currentUser.role === 'admin') addTab('admin-users', 'shield-alert', 'Gestão de Acessos');
lucide.createIcons(); window.switchTab(currentUser.role === 'spacecom' ? 'spacecom-triagem' : (currentUser.role === 'nucleo' ? 'nucleos' : 'dashboard'));
if (currentUser.role === 'spacecom') setTimeout(() => window.ensureSpacecomSaidaSelects(), 250);
// Esconde as caixas de observação/órgão para a Spacecom de forma universal
if (currentUser.role === 'spacecom') {
setTimeout(() => {
['conf-obs', 'envio-obs', 'recebimento-obs'].forEach(id => {
const el = document.getElementById(id);
if (el && el.parentElement) el.parentElement.style.display = 'none';
});
}, 300);
}
const confOrigem = $('conf-origem');
if (confOrigem) {
if (currentUser.role === 'admin' || currentUser.role === 'central') {
confOrigem.innerHTML = 'Material Novo (Fornecedor) Material Triado (Spacecom) ';
} else if (currentUser.role !== 'spacecom') {
confOrigem.innerHTML = 'Material Novo (Enviado pela Central) Material Triado (Spacecom) ';
}
}
document.querySelectorAll('.select-unidades').forEach(el => {
let html = '';
if(['admin', 'central'].includes(currentUser.role)) {
html += 'Estoque Central BH ';
if (typeof nucleosNames !== 'undefined') {
nucleosNames.forEach(n => html += `${n} `);
}
} else if(currentUser.role === 'nucleo') {
const unidadesPermitidas = (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : (
Array.isArray(currentUser.unidades)
? currentUser.unidades
: (typeof currentUser.unidades === 'string'
? currentUser.unidades.split(',').map(u => u.trim()).filter(Boolean)
: (currentUser.unidade ? [currentUser.unidade] : []))
));
if (unidadesPermitidas.length > 0) {
unidadesPermitidas.forEach(n => html += `${n} `);
}
} else if(currentUser.role === 'spacecom') {
// Preenche as caixas para a Spacecom automaticamente
if(el.id.includes('origem')) {
html = 'Spacecom ';
} else {
html = 'Equipamentos triados ';
}
}
el.innerHTML = html;
});
const relNuc = $('rel-unidade');
if(relNuc){
relNuc.innerHTML = '';
const unidadesUsuario = Array.isArray(currentUser.unidades)
? currentUser.unidades
: (typeof currentUser.unidades === 'string'
? currentUser.unidades.split(',').map(s => s.trim()).filter(Boolean)
: []);
const isGestao = currentUser.role === 'admin';
if (isGestao) {
relNuc.innerHTML += 'TODAS AS UNIDADES DO ESTADO ';
relNuc.innerHTML += 'Central BH ';
nucleosNames.forEach(n => relNuc.innerHTML += `${n} `);
} else {
const visiveis = currentUser.role === 'nucleo' && typeof window.obterUnidadesHierarquicasUsuario === 'function'
? window.obterUnidadesHierarquicasUsuario()
: (unidadesUsuario.length ? unidadesUsuario : ['Central BH']);
visiveis.forEach(un => relNuc.innerHTML += `${un} `);
}
}
if (typeof window.popularFiltrosUsoDiarioUnidade === 'function') {
window.popularFiltrosUsoDiarioUnidade();
}
const selNuc = $('nucleo-select'); if(selNuc){ selNuc.innerHTML = ''; let allw = currentUser.role !== 'nucleo' ? nucleosNames : (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : (currentUser.unidades||[])); if(allw.length > 1 || currentUser.role !== 'nucleo') { selNuc.innerHTML += 'TODOS NÚCLEOS/SUBNÚCLEOS '; } allw.forEach(n => selNuc.innerHTML += `${n} `); }
if(currentUser.role === 'nucleo') {
const unidadesHierarquicasUsuario = window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : (currentUser.unidades || []);
if(unidadesHierarquicasUsuario.length <= 1) { $('filtro-nucleo-container').classList.add('hidden'); } else { $('filtro-nucleo-container').classList.remove('hidden'); }
['btn-nova-devolucao', 'btn-transferencia'].forEach(id => { const b = $(id); if(b) b.classList.remove('hidden'); });
const unidadesUsuario = unidadesHierarquicasUsuario;
const possuiUnidadeLiberadaPedido = unidadesUsuario.some(u => window.unidadePodeSolicitarCentral ? window.unidadePodeSolicitarCentral(u) : false);
const btnNovoPedido = $('btn-novo-pedido');
if (btnNovoPedido) {
if (possuiUnidadeLiberadaPedido) btnNovoPedido.classList.remove('hidden');
else btnNovoPedido.classList.add('hidden');
}
} else { if(currentUser.role === 'admin' || currentUser.role === 'central') { ['btn-config-limites', 'btn-config-limites-central', 'btn-force-sync'].forEach(id => { const b = $(id); if(b) b.classList.remove('hidden'); }); } ['btn-transferencia', 'btn-novo-pedido'].forEach(id => { const b = $(id); if(b) b.classList.remove('hidden'); }); }
if(currentUser.role === 'admin') { renderAdminUsers(); ['btn-zerar-pedidos', 'btn-arquivar-concluidos', 'btn-arquivar-concluidos-dev'].forEach(id => { const b = $(id); if(b) b.classList.remove('hidden'); }); }
const bellIcon = $('notification-bell'); if (bellIcon) bellIcon.classList.remove('hidden'); updateNotifications();
}
// ============================================================================
// IA PROFISSIONAL DO SIGME - SISTEMA COMPLETO COM MEMÓRIA, PERMISSÕES E APRENDIZADO
// ============================================================================
// ESTRUTURAS DE DADOS PROFISSIONAIS
window.iaState = {
abaAtual: 'dashboard',
historico: [], // Memória conversacional
ultimaResposta: null,
usuarioFrustrado: false,
logPerguntas: [], // Rastreamento de perguntas não entendidas
fluxoAtual: {} // Estado do fluxo do usuário
};
// MAPA DE PERMISSÕES POR ROLE
window.permissoesPorRole = {
'admin': ['autorizar', 'preparar', 'despachar', 'confirmar', 'transferir', 'abater', 'gerar_relatorio', 'deletar'],
'central': ['autorizar', 'preparar', 'despachar', 'confirmar', 'transferir', 'gerar_relatorio'],
'nucleo': ['solicitar', 'devolver', 'consultar', 'gerar_recibo'],
'spacecom': ['receber', 'inspecionar', 'reparar', 'despachar_reparo']
};
// MAPA DE ESTADOS E PRÉ-CONDIÇÕES POR OPERAÇÃO
window.precondoesPorOperacao = {
'autorizar_pedido': {
estadoRequerido: 'Análise',
permissao: ['admin', 'central'],
verificar: (contexto) => {
return contexto.pedidoAtual && contexto.pedidoAtual.status === 'Análise';
}
},
'preparar_malote': {
estadoRequerido: 'Preparação',
permissao: ['admin', 'central'],
verificar: (contexto) => contexto.pedidoAtual?.status === 'Autorizado'
},
'confirmar_devolucao': {
estadoRequerido: 'Itens Bipados',
permissao: ['admin', 'central', 'nucleo'],
verificar: (contexto) => contexto.seriaisBipados && contexto.seriaisBipados.length > 0
},
'fazer_pedido': {
estadoRequerido: 'Saldo Disponível',
permissao: ['admin', 'central', 'nucleo'],
verificar: (contexto) => {
const saldoDisponivel = Object.values(contexto.estoque.central || {})
.reduce((a, b) => a + (b.atual || 0), 0) > 0;
return saldoDisponivel;
}
}
};
// MAPA DE FLUXOS ESPERADOS (para sugerir próximos passos)
window.fluxosEsperados = {
'pedido_suprimento': ['solicitar', 'autorizar', 'preparar', 'despachar', 'confirmar_recebimento'],
'devolucao': ['registrar_saida', 'bipar_seriais', 'marcar_status', 'confirmar'],
'auditoria': ['colar_sac24', 'bipar_fisico', 'cruzar_dados', 'confirmar_abate'],
'admissao': ['preencher_dados', 'bipar_equipamentos', 'gerar_cautela', 'assinar'],
'plantao': ['registrar_movimentacoes', 'consolidar_dados', 'gerar_termo', 'exportar_pdf']
};
window.monitorarAbas = () => {
const abas = ['dashboard', 'fluxo-estoque', 'nucleos', 'logistica', 'devolucoes', 'auditoria', 'admissao', 'utilizados', 'outros-estados', 'relatorio', 'plantao', 'spacecom-recebimento'];
abas.forEach(aba => {
const el = document.getElementById(aba);
if (el && el.classList.contains('active')) {
window.iaState.abaAtual = aba;
}
});
};
// ANÁLISE PROFISSIONAL DE INTENÇÃO COM CONTEXTO
window.analisarIntencaoProfunda = (pergunta, historico = []) => {
const p = window.normalizarTextoIA(pergunta);
// Verificar se é continuação de conversa
let ehContinuacao = false;
let contextoAnterior = null;
if (historico.length > 0) {
const padroesContinuacao = ['e depois', 'depois', 'e então', 'próximo', 'qual é o próximo', 'como assim', 'isso que você', 'mais detalhes', 'me explica melhor', 'não entendi'];
if (padroesContinuacao.some(p2 => p.includes(p2))) {
ehContinuacao = true;
contextoAnterior = historico[historico.length - 1];
}
}
// Detectar frustração/estado emocional
const padroesFrustacao = [
/!{2,}|ñ+|tá ruim|não consigo|não funciona|que ódio|chato|isso é|vai\s+ficar|pra que|tá complicado|não tá dando/i,
/\b[A-ZÀ-Ú]{5,}\b/ // Detectar gritaria sem punir siglas curtas como KPI
];
const temFrustacao = padroesFrustacao.some(p2 =>
typeof p2 === 'string' ? p.includes(p2) : p2.test(pergunta)
);
// Mapeamento de intenções EXPANDIDO com sinonímia
const intentos = {
envio: ['enviar', 'envio', 'manda', 'pedido', 'solicitar', 'suprimento', 'despach', 'logistica', 'remessa', 'novo pedido', 'autorizar', 'preparar', 'despachar', 'material', 'solicit', 'confirmar chegada', 'receber remessa'],
devolucao: ['devolu', 'reversa', 'voltar', 'devolver', 'retorn', 'traze', 'recuperar', 'defeito', 'devolv', 'remessa de volta'],
auditoria: ['auditoria', 'pagos', 'erario', 'abat', 'recupera', 'indeniz', 'cruzamento', 'sac24', 'alerta de erário'],
admissao: ['admissao', 'uso', 'entrega', 'saida', 'cautela', 'policial', 'registrar', 'uso diário', 'masp'],
spacecom: ['spacecom', 'triagem', 'manuten', 'consert', 'fabrica', 'inspec', 'recebimento', 'recibimento', 'aceite', 'reparacao', 'dar entrada', 'entrada de equipamento', 'entrada em equipamento'],
dashboard: ['executiv', 'dash', 'kpi', 'indicador', 'metrica', 'grafico', 'relat', 'resumo', 'visao geral', 'cor no grafico'],
fluxo: ['fluxo', 'mapa', 'calor', 'regional', 'polo', 'sac24', '3d'],
plantao: ['plantão', 'passag', 'turno', 'encerr', 'termo'],
nsi: ['nsi', 'admissão/nsi', 'admissão nsi', 'núcleo de suporte'],
preenchimento: ['preencha', 'como preenche', 'qual campo', 'onde coloca', 'como digita', 'qual dado', 'campo obrigatorio', 'o que escrevo', 'qual é o format', 'como preencho', 'qual informa'],
consulta: ['quanto', 'qual', 'quantos', 'quais', 'status', 'situacao', 'tem', 'existe', 'ha', 'tem algum', 'me mostra', 'preciso saber', 'estoque total', 'estoque atual'],
problema: ['problema', 'erro', 'bug', 'nao funciona', 'crash', 'quebro', 'falha', 'issue', 'parou', 'não funciona', 'travou', 'não abre', 'não carrega'],
ajuda: ['ajuda', 'help', 'manual', 'tutorial', 'como faz', 'como usar', 'como faço', 'passo', 'etapa', 'me ajuda', 'explica'],
validacao: ['valida', 'incompatível', 'não combina', 'serial errado', 'serial inválido', 'serial não existe', 'não é esse']
};
let intentoDetectado = [];
let scoreConfianca = 0;
for (const [tipo, palavras] of Object.entries(intentos)) {
const matches = palavras.filter(pal => p.includes(window.normalizarTextoIA(pal))).length;
if (matches > 0) {
intentoDetectado.push(tipo);
scoreConfianca = Math.max(scoreConfianca, (matches / palavras.length) * 100);
}
}
if (intentoDetectado.includes('consulta') && intentoDetectado.length > 1) {
intentoDetectado = intentoDetectado.filter(tipo => tipo !== 'consulta');
}
return {
intentos: intentoDetectado,
perguntaOriginal: pergunta,
perguntaNormalizada: p,
ehContinuacao,
contextoAnterior,
temFrustacao,
scoreConfianca,
ehConsulta: intentoDetectado.includes('consulta'),
ehProblema: intentoDetectado.includes('problema'),
ehAjuda: intentoDetectado.includes('ajuda'),
ehPreenchimento: intentoDetectado.includes('preenchimento'),
ehValidacao: intentoDetectado.includes('validacao')
};
};
// VALIDAR PERMISSÕES
window.validarPermissao = (operacao, userRole) => {
const permsRequeridas = window.permissoesPorRole[userRole] || [];
return permsRequeridas.includes(operacao);
};
// VERIFICAR PRÉ-CONDIÇÕES
window.verificarPrecondoes = (operacao, contexto) => {
const precond = window.precondoesPorOperacao[operacao];
if (!precond) return { valido: true, mensagem: '' };
const temPermissao = precond.permissao.includes(contexto.usuarioAtual.role);
const estadoValido = precond.verificar(contexto);
if (!temPermissao) {
return {
valido: false,
tipo: 'PERMISSAO_NEGADA',
mensagem: `❌ Seu perfil (${contexto.usuarioAtual.role.toUpperCase()}) não tem permissão para: ${operacao.replace(/_/g, ' ')}`
};
}
if (!estadoValido) {
return {
valido: false,
tipo: 'PRE_CONDICAO_NAO_ATENDIDA',
mensagem: `⚠️ Você precisa estar em: "${precond.estadoRequerido}" para fazer isso.`
};
}
return { valido: true, mensagem: '' };
};
// SUGERIR PRÓXIMOS PASSOS INTELIGENTES
window.sugerirProximoPasso = (operacaoAtual, fluxo) => {
if (!fluxo || !window.fluxosEsperados[fluxo]) return null;
const passos = window.fluxosEsperados[fluxo];
const indiceAtual = passos.indexOf(operacaoAtual);
if (indiceAtual >= 0 && indiceAtual < passos.length - 1) {
const proximoPasso = passos[indiceAtual + 1];
const descricoes = {
'solicitar': 'Solicitar novo material à Central',
'autorizar': 'Autorizar o pedido (Central)',
'preparar': 'Preparar o malote',
'despachar': 'Despachar para o destino',
'confirmar_recebimento': 'Confirmar chegada do material',
'bipar_seriais': 'Bipar os seriais dos equipamentos',
'marcar_status': 'Marcar se é Operacional ou Defeito',
'confirmar': 'Confirmar a operação',
'colar_sac24': 'Colar lista SAC24 de indenizados',
'bipar_fisico': 'Bipar equipamentos recuperados',
'cruzar_dados': 'Processar cruzamento',
'confirmar_abate': 'Confirmar abate de patrimônio',
'preencher_dados': 'Preencher dados do policial (Nome, MASP, Data)',
'bipar_equipamentos': 'Bipar os equipamentos entregues',
'gerar_cautela': 'Gerar cautela PDF',
'assinar': 'Assinar digitalmente',
'registrar_movimentacoes': 'Registrar todas as movimentações do dia',
'consolidar_dados': 'Consolidar dados das 24h',
'gerar_termo': 'Gerar termo de plantão',
'exportar_pdf': 'Exportar PDF assinado'
};
return {
proximoPasso,
descricao: descricoes[proximoPasso] || proximoPasso,
pergunta: `Como ${descricoes[proximoPasso]?.toLowerCase() || proximoPasso}?`
};
}
return null;
};
window.obterNomeAbaExibicao = (abaId) => {
const role = currentUser?.role || 'nucleo';
const nomes = {
dashboard: 'Gestão Executiva',
'fluxo-estoque': 'Fluxo de Estoque 3D',
nucleos: role === 'nucleo' ? 'Meu Estoque' : 'Gestão Regional',
logistica: role === 'nucleo' ? 'Meus Pedidos' : 'Envios Núcleos/Subnúcleos',
devolucoes: role === 'nucleo' ? 'Devolver Material' : 'Devoluções para Central',
auditoria: 'Auditoria - Equipamentos Pagos',
admissao: 'Admissão / Uso Diário',
utilizados: 'Equipamentos em Uso',
'outros-estados': 'Outros Estados',
relatorio: 'Relatórios BI',
plantao: 'Passagem de Plantão',
'spacecom-recebimento': 'Recebimento Spacecom',
'spacecom-triagem': 'Gestão de Triagem',
movimentar: 'Entrada/Saída Spacecom'
};
return nomes[abaId] || window.perguntasPorAba?.[abaId]?.nome || 'Dashboard';
};
window.normalizarRespostaIA = (texto) => {
const nomeAbaEnvios = window.obterNomeAbaExibicao('logistica');
return String(texto || '')
.replace(/Aba\s+"Logística"/gi, `Aba "${nomeAbaEnvios}"`)
.replace(/via\s+Logística/gi, `pela aba "${nomeAbaEnvios}"`)
.replace(/Painel de Gestão Logística/gi, nomeAbaEnvios)
.replace(/\baba\s+logistica\b/gi, `aba ${nomeAbaEnvios}`)
.replace(/\baba\s+Logística\b/gi, `aba ${nomeAbaEnvios}`);
};
window.normalizarTextoIA = (texto) => String(texto || '').toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
window.localizarUnidadeNaPergunta = (pergunta) => {
const p = window.normalizarTextoIA(pergunta);
const todas = ['Central BH', ...(nucleosNames || [])];
return todas.find(unidade => {
const normalizada = window.normalizarTextoIA(unidade)
.replace(/^nucleo\s+/, '')
.replace(/^subnucleo\s+/, '')
.replace(/^extensao\s+/, '');
return p.includes(window.normalizarTextoIA(unidade)) || (normalizada && p.includes(normalizada));
}) || null;
};
// OBTER CONTEXTO COMPLETO PARA RESPOSTAS
window.extrairContextoCompleto = () => {
window.monitorarAbas();
const contexto = {
usuarioAtual: {
nome: currentUser?.nome || 'Usuário',
role: currentUser?.role || 'nucleo',
unidades: currentUser?.unidades || [],
masp: currentUser?.masp || 'N/A'
},
abaAtual: window.iaState.abaAtual,
abaNome: window.obterNomeAbaExibicao(window.iaState.abaAtual),
estoque: {
central: window.dbData?.inventory || {},
nucleos: window.dbData?.nucleosInventory || {},
transferencias: window.dbData?.transferencias || [],
movimentacoes: window.dbData?.movimentosManuais || []
},
estatisticas: {
totalAtivos: itensConfig.reduce((acc, item) => acc + ((window.dbData?.inventory || {})[item]?.atual || 0), 0),
totalTransito: (window.dbData?.transferencias || []).filter(t => t.status === 'Em Trânsito').length,
totalPendente: (window.dbData?.movimentosManuais || []).filter(m => m.status === 'Pendente').length,
totalItens: itensConfig.length,
emRupturaTotal: itensConfig
.filter(item => (((window.dbData?.inventory || {})[item]?.atual || 0) <= ((window.dbData?.inventory || {})[item]?.baixo || 0)) && (((window.dbData?.inventory || {})[item]?.baixo || 0) > 0))
}
};
return contexto;
};
// MAPA DE PERGUNTAS SUGERIDAS POR ABA
window.perguntasPorAba = {
'dashboard': {
nome: 'Gestão Executiva',
perguntas: [
'Qual é meu estoque total?',
'Tem algo crítico?',
'Quantos itens estão em trânsito?',
'Como funciona os KPIs?',
'O que significa cada cor no gráfico?'
],
dicas: 'Este painel mostra os 5 KPIs principais: Ativos (SAC24), Físico (SIGME), Trânsito, Divergências e Ruptura.'
},
'fluxo-estoque': {
nome: 'Fluxo de Estoque 3D',
perguntas: [
'Como importar dados do SAC24?',
'O que significa o mapa de calor?',
'Qual cor é crítica?',
'Como as metas são calculadas?',
'Como exportar o mapa PDF?'
],
dicas: 'Aqui você visualiza o mapa regional com status de cada polo (Crítico, Atenção, Operacional, Superávit).'
},
'nucleos': {
nome: 'Gestão Regional',
perguntas: [
'Qual é o estoque do meu núcleo?',
'Tem degradação de bateria?',
'Como configurar metas?',
'Como transferir para outro polo?',
'Qual núcleo tem mais disponibilidade?'
],
dicas: 'Monitore os estoques do interior. Alertas de "Degradação de Bateria" aparecem para equipamentos parados > 30 dias.'
},
'logistica': {
nome: 'Envios Núcleos/Subnúcleos',
perguntas: [
'Como solicitar um novo pedido?',
'Como autorizar um pedido?',
'Como preparar o malote?',
'Como despachar equipamento?',
'Como confirmar chegada de remessa?'
],
dicas: 'Fluxo: Análise → Preparação → Recibo → Concluído. Bipe os seriais em cada etapa.'
},
'devolucoes': {
nome: 'Devoluções para Central',
perguntas: [
'Como fazer uma devolução?',
'Qual é a diferença entre Operacional e Defeito?',
'Como marcar como defeito?',
'Onde bipo os seriais?',
'Como confirmar a devolução?'
],
dicas: 'Defina se é Operacional (volta ao estoque) ou Defeito (vai para triagem). Sempre bipe os seriais reais.'
},
'auditoria': {
nome: 'Auditoria - Equipamentos Pagos',
perguntas: [
'Como colar a lista SAC24?',
'O que significa o alerta de erário?',
'Como fazer o cruzamento?',
'Como confirmar abate de equipamento?',
'Qual é o formato da lista de indenizados?'
],
dicas: 'Esquerda: cola lista SAC24 | Direita: bipa equipamentos físicos recuperados | Processa cruzamento.'
},
'admissao': {
nome: 'Admissão / Uso Diário',
perguntas: [
'Como registrar uma entrega de equipamento?',
'Qual policial está usando?',
'Como preencher o MASP?',
'Como bipar os seriais?',
'Como gerar a cautela assinada?'
],
dicas: 'Preencha: Nome do Policial, MASP, Data, Unidade. Depois bipe os seriais e confirme.'
},
'utilizados': {
nome: 'Equipamentos em Uso',
perguntas: [
'Qual policial tem qual equipamento?',
'Quanto tempo está em uso?',
'Como recuperar um equipamento?',
'Há equipamentos vencidos em uso?',
'Como filtrar por unidade?'
],
dicas: 'Visualize todos os equipamentos entregues a policiais. Identifique os que precisam retornar.'
},
'outros-estados': {
nome: 'Outros Estados',
perguntas: [
'Como registrar equipamento de outro estado?',
'Qual é o serial?',
'Qual estado é?',
'Qual órgão responsável?',
'Como exportar relatório?'
],
dicas: 'Registre equipamentos com origem fora de MG. Preencha: Serial, Estado, Órgão, Endereço.'
},
'relatorio': {
nome: 'Relatórios BI',
perguntas: [
'Como extrair relatório histórico?',
'Qual período devo selecionar?',
'Como filtrar por unidade?',
'Como exportar em CSV?',
'Qual é o relatório de devoluções?'
],
dicas: 'Todos os relatórios podem ser filtrados e exportados. Escolha o tipo desejado no menu.'
},
'plantao': {
nome: 'Passagem de Plantão',
perguntas: [
'Como gerar termo de plantão?',
'O que inclui no documento?',
'Como assinar eletronicamente?',
'Qual é a data de vigência?',
'Como exportar o PDF?'
],
dicas: 'Consolida 24h de operações. Inclui todas as movimentações, nomes e assinaturas digitais.'
},
'spacecom-recebimento': {
nome: 'Recebimento Spacecom',
perguntas: [
'Como registrar recebimento de equipamentos?',
'O que é um recibo Spacecom?',
'Como bipar e validar seriais?',
'Como gerar PDF de aceite?',
'O que fazer se algum serial não valida?'
],
dicas: 'Carregue recibo, valide seriais, gere PDF com QR code eletrônico para assinatura.'
}
};
window.obterAbaAtualInfo = () => {
window.monitorarAbas();
const abaId = window.iaState?.abaAtual || 'dashboard';
const base = window.perguntasPorAba[abaId] || window.perguntasPorAba['dashboard'];
return {
...base,
nome: window.obterNomeAbaExibicao(abaId)
};
};
window.atualizarSugestoesPerguntas = () => {
const abaInfo = window.obterAbaAtualInfo();
const container = document.querySelector('[id^="sugestoes-ia"]') || document.getElementById('sugestoes-ia');
if (!container) return;
container.innerHTML = `
📍 Aba Atual: ${abaInfo.nome}
${abaInfo.dicas}
${abaInfo.perguntas.map(q => `
💬 ${q}
`).join('')}
`;
lucide.createIcons();
};
window.extrairContextoSistema = () => {
const contexto = {
usuarioAtual: {
nome: currentUser?.nome || 'Usuário',
role: currentUser?.role || 'nucleo',
unidades: currentUser?.unidades || [],
masp: currentUser?.masp || 'N/A'
},
abaAtual: window.obterAbaAtualInfo().nome,
estoque: {
central: window.dbData?.inventory || {},
nucleos: window.dbData?.nucleosInventory || {},
transferencias: window.dbData?.transferencias || [],
movimentacoes: window.dbData?.movimentosManuais || []
},
estatisticas: {
totalAtivos: Object.values(window.dbData?.inventory || {}).reduce((a, b) => a + (b.atual || 0), 0),
totalTransito: (window.dbData?.transferencias || []).filter(t => t.status === 'Em Trânsito').length,
totalPendente: (window.dbData?.movimentosManuais || []).filter(m => m.status === 'Pendente').length,
totalItens: Object.keys(window.dbData?.inventory || {}).length
}
};
return contexto;
};
window.analisarIntencaoPergunta = (pergunta) => {
const p = pergunta.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
const intentos = {
envio: ['enviar', 'envio', 'manda', 'pedido', 'solicitar', 'suprimento', 'despach', 'logística', 'remessa', 'novo pedido', 'autorizar', 'preparar', 'despachar'],
devolucao: ['devolu', 'reversa', 'voltar', 'devolver', 'retorn', 'traze', 'recuperar', 'defeito'],
auditoria: ['auditoria', 'pagos', 'erario', 'abat', 'recupera', 'indeniz', 'cruzamento', 'sac24'],
admissao: ['admissao', 'uso', 'entrega', 'saida', 'cautela', 'policial', 'registrar'],
spacecom: ['spacecom', 'triagem', 'manuten', 'consert', 'fabrica', 'inspec', 'recibimento', 'aceite'],
dashboard: ['executiv', 'dash', 'kpi', 'indicador', 'metrica', 'gráfico', 'relat'],
fluxo: ['fluxo', 'mapa', 'calor', 'regional', 'interior', 'nucleo', 'polo', 'sac24'],
plantao: ['plantão', 'passag', 'turno', 'encerr'],
nsi: ['nsi', 'admissão/nsi', 'admissão nsi'],
preenchimento: ['preencha', 'como preenche', 'qual campo', 'onde coloca', 'como digita', 'qual dado', 'campo obrigatorio', 'o que escrevo'],
consulta: ['quanto', 'qual', 'quantos', 'como', 'quais', 'status', 'situacao', 'tem', 'existe', 'há', 'tem algum'],
problema: ['problema', 'erro', 'bug', 'nao funciona', 'crash', 'quebro', 'falha', 'issue', 'parou', 'nao consigo'],
ajuda: ['ajuda', 'help', 'manual', 'tutorial', 'como faz', 'como usar', 'como faço', 'passo', 'etapa']
};
let intentoDetectado = [];
for (const [tipo, palavras] of Object.entries(intentos)) {
if (palavras.some(pal => p.includes(pal))) {
intentoDetectado.push(tipo);
}
}
return {
intentos: intentoDetectado,
perguntaOriginal: pergunta,
perguntaNormalizada: p,
ehConsulta: intentoDetectado.includes('consulta'),
ehProblema: intentoDetectado.includes('problema'),
ehAjuda: intentoDetectado.includes('ajuda'),
ehPreenchimento: intentoDetectado.includes('preenchimento')
};
};
// ============================================================================
// GERAÇÃO PROFISSIONAL DE RESPOSTA - COM MEMÓRIA E VALIDAÇÕES
// ============================================================================
// DEFINIR TODAS AS FUNÇÕES DE RESPOSTA ANTES DE USAR
const gerarRespostaPreenchimento = (pergunta, contexto) => {
const campos = {
'nome': 'Campo de Nome (sempre completo e legível)',
'masp': 'Número de MASP = Matrícula Oficial do Servidor (9 dígitos)',
'data': 'Formato: DD/MM/AAAA (ex: 23/06/2026)',
'serial': 'Código do equipamento (sem espaços, tal qual no rótulo)',
'unidade': 'Selecione a unidade no dropdown - escolha uma da lista',
'quantidade': 'Número inteiro (1, 2, 3... etc)',
'status': 'Operacional (funciona) ou Defeito (problema técnico)'
};
let campo = Object.keys(campos).find(c => pergunta.toLowerCase().includes(c));
if (!campo) campo = 'campo';
return `📝 Preenchimento de Campo: ${campos[campo] || campo}
Regras:
✅ Campos com * são obrigatórios
✅ Sem espaços desnecessários
✅ Use MAIÚSCULA onde indicado
✅ Se errar, edite e salve novamente
Exemplo: "POLICIAL PENAL SILVA SANTOS" ou "123456789"`;
};
const gerarRespostaProblema = (pergunta, contexto) => {
return `🔧 Troubleshooting Profissional:
Passo 1: Atualize a página
• PC: Ctrl+Shift+Delete e selecione "Limpar tudo"
• Mac: Cmd+Shift+Delete
Passo 2: Se o problema persistir:
• Tente outro navegador (Chrome, Firefox, Edge)
• Verifique sua conexão com internet
Passo 3: Se ainda assim não funcionar:
• Contate: suportedgme@gmail.com
• Descreva: Qual aba? Qual ação? Qual é o erro?
• Foto da tela ajuda muito!
Status Atual do Sistema: ✅ Online e Operacional`;
};
const gerarRespostaValidacao = (pergunta, contexto) => {
return `⚠️ Validação de Dados:
Serial Não Valida - Causas Possíveis:
1. Serial errado: Releia no rótulo do equipamento
2. Serial duplicado: Já foi inserido antes
3. Formato inválido: Deve ter exatamente 9 dígitos
4. Equipamento não está no sistema: Contate Central
Solução: Valide manualmente o serial real do equipamento físico!`;
};
const gerarRespostaEnvio = (pergunta, contexto) => {
const nomeAbaEnvios = window.obterNomeAbaExibicao('logistica');
return `📦 Fluxo Completo de Envio (Suprimento):
🟢 PASSO 1 - Solicitar (Seu Núcleo):
1. Aba "${nomeAbaEnvios}" → Clique "Novo Pedido"
2. Selecione os itens e quantidades
3. Confirme → Sistema envia para análise
🟡 PASSO 2 - Autorizar (Central BH):
1. Central visualiza pedido em "Análise"
2. Verifica saldo disponível
3. Clica "Autorizar" ou "Negar"
🟠 PASSO 3 - Preparar (Central):
1. Pedido vai para "Preparação"
2. Clique "Preparar Malote"
3. ⚠️ Bipe os seriais REAIS do equipamento físico
4. Se serial não combina, use o serial real mesmo assim
🔵 PASSO 4 - Despachar (Central):
1. Após bipar todos, clique "Despachar Malote"
2. Preencha dados do transportador
3. Confirme despacho
⚫ PASSO 5 - Receber (Seu Núcleo):
1. Você recebe o malote
2. Clique "Confirmar Chegada"
3. Items integram ao seu estoque
Tempo Estimado: 2-3 dias úteis`;
};
const gerarRespostaDevolucao = (pergunta, contexto) => {
return `📥 Como Fazer Devolução para Central BH:
IMPORTANTE: Defina o status CORRETAMENTE!
🟢 OPERACIONAL = Funcionando
• Equipamento sem problemas
• Volta ao estoque disponível
• Pode ser usado por outro policial
🔴 DEFEITO = Com Problema
• Equipamento com mal funcionamento
• Vai para triagem da Spacecom
• Sai do seu saldo permanentemente
• Retorna apenas após manutenção
Passo-a-Passo:
1. Aba "Devoluções" → "Remessa p/ BH"
2. Marque cada equipamento: Operacional ou Defeito
3. Bipe os seriais reais
4. Clique "Efetivar Remessa"
5. Material entra em "Trânsito para BH"
Dica: Quando em dúvida, marque como DEFEITO. Melhor conservador!`;
};
const gerarRespostaAuditoria = (pergunta, contexto) => {
return `🔍 Auditoria - Alerta de Erário:
O que é? Um alerta crítico quando você bipa um equipamento que o Estado JÁ PAGOU à Spacecom (considerado "Perdido").
Significado: Você encontrou um equipamento que estava "morto" (indenizado) mas apareceu fisicamente!
Processo de Auditoria:
1. Colar Lista SAC24:
• Procure planilha SAC24 com indenizados
• Copie APENAS os seriais
• Cole no campo à esquerda
2. Bipar Equipamentos Recuperados:
• Use câmera ou digite manualmente
• Bipe cada serial encontrado à direita
3. Processar Cruzamento:
• Clique "Processar Cruzamento"
• Sistema mostra: ✅ Encontrado ou ❌ Não Encontrado
4. Confirmar Abate:
• Se há encontrados, clique "Confirmar Abate"
• Patrimônio oficialmente recuperado para PPMG!
Impacto: Reduz quebra de estoque e valoriza patrimônio`;
};
const gerarRespostaAdmissao = (pergunta, contexto) => {
return `📋 Admissão / Uso Diário - Registrar Entrega:
O que é? Registrar quando você entrega um equipamento a um policial para uso durante seu turno.
Preencher - Campos Obrigatórios (*):
* Nome do Policial: Completo e legível
Exemplo: "POLICIAL PENAL SILVA SANTOS"
* MASP: Matrícula Oficial (9 dígitos)
Exemplo: "123456789"
* Data do Uso: Quando saiu para uso
Formato: DD/MM/AAAA (ex: 23/06/2026)
* Unidade Fornecedora: De onde saiu o equipamento
Exemplo: "Núcleo Divinópolis" ou "Central BH"
Inserir Seriais (Pelo menos 1):
1. Clique "Inserir Seriais"
2. Use câmera para QR code OU digite manualmente
3. Um serial por linha
Finalizar:
1. Revise dados e seriais
2. Clique "Confirmar Saídas"
3. PDF com cautela será gerado
4. Equipamentos saem do estoque
⚠️ Importante: Sempre use os seriais REAIS do equipamento!`;
};
const gerarRespostaSpacecom = (pergunta, contexto) => {
return `🏭 Recebimento Spacecom / Triagem:
O que é? Lote novo de fábrica ou equipamentos retornando de conserto.
Passo 1 - Carregar Recibo:
• Sistema mostra recibos pendentes
• Selecione o lote a validar
Passo 2 - Validar Seriais:
1. Para cada serial na nota fiscal:
2. Use câmera para QR code OU digite
3. Sistema verifica em tempo real
4. ✅ Válido = continua
5. ❌ Inválido = registre divergência
Passo 3 - Gerar PDF Aceite:
• Quando todos validados
• Clique "Gerar PDF Aceite"
• PDF contém QR code eletrônico
Passo 4 - Assinatura Digital:
• Transportador assina o PDF
• Você assina também
• Documento arquivado automaticamente
Serial Não Valida?
• Verifique o código visual no equipamento
• Se ilegível, fotografe e registre
• Notifique gerente de recebimento`;
};
const gerarRespostaRecebimento = (pergunta, contexto) => {
const p = window.normalizarTextoIA(pergunta);
if (contexto.abaAtual === 'spacecom-recebimento' || contexto.abaAtual === 'movimentar' || p.includes('spacecom') || p.includes('dar entrada')) {
return `📥 Como Dar Entrada em Equipamentos:
Se for recebimento pela Spacecom:
1. Abra a aba "${window.obterNomeAbaExibicao('spacecom-recebimento')}"
2. Selecione o recibo pendente ou use o modo manual
3. Cole ou importe os seriais esperados
4. Bipar os seriais recebidos
5. Clique em "Verificar Recebimento"
6. Gere o PDF de recebimento e confirme
Se for entrada física no estoque:
Use a aba "${window.obterNomeAbaExibicao('movimentar')}" para lançar entrada/saída e conferência.`;
}
return `📦 Como Receber Equipamentos no Núcleo:
Quando chegar uma remessa enviada pela Central:
1. Abra a aba "${window.obterNomeAbaExibicao('logistica')}"
2. Localize o malote enviado para sua unidade
3. Clique em "Confirmar Chegada"
4. Confira os itens/seriais recebidos
5. Confirme para integrar ao estoque
Se você quis dizer dar entrada por recebimento técnico:
Use a aba "${window.obterNomeAbaExibicao('spacecom-recebimento')}".`;
};
const gerarRespostaDashboard = (pergunta, contexto) => {
const criticos = contexto.estatisticas.emRupturaTotal || [];
return `📊 Gestão Executiva - Dashboard Estratégico:
5 KPIs Monitorados:
🔵 Ativos (SAC24): Equipamentos em uso real conforme SAC24
📦 Físico (SIGME): Equipamentos realmente em suas prateleiras
🚚 Trânsito: Equipamentos dentro de malotes em traslado
⚠️ Divergências: SAC24 ≠ SIGME = Quebra de Estoque
📉 Ruptura: Quantidade faltante para atingir meta segura
Status Atual:
• Total em Estoque: ${contexto.estatisticas.totalAtivos || 0}
• Em Trânsito: ${contexto.estatisticas.totalTransito || 0} remessas
• Pendente: ${contexto.estatisticas.totalPendente || 0} operações
${criticos.length > 0 ? `• CRÍTICOS: ${criticos.join(', ')} ` : '• Nenhum item crítico! ✅ '}
Como Usar: Monitore diariamente para antecipar faltas!`;
};
const gerarRespostaFluxo = (pergunta, contexto) => {
return `🗺️ Gestão de Fluxo 3D - Mapa Regional:
O que é? Mapa de Minas Gerais mostrando saúde de cada polo regional.
Cores do Mapa:
🔴 Vermelho = CRÍTICO: Abaixo do mínimo, risco iminente
🟠 Laranja = ATENÇÃO: Abaixo da meta, mas operacional
🟢 Verde = OPERACIONAL: Dentro da meta de segurança
🔵 Azul = SUPERÁVIT: Mais do que o necessário
⚫ Preto = SEM DADOS: Nunca recebeu informações
Como Importar SAC24:
1. Clique "Base Inteligente SAC24"
2. Selecione arquivo com dados de ativações
3. Sistema processa automaticamente
4. Mapa atualiza com novas metas
Estratégia:
• Região vermelha? Solicite transferência de superávit!
• Reequilibre automaticamente
• Minimize ruptura de estoque`;
};
const gerarRespostaPlantao = (pergunta, contexto) => {
return `📋 Passagem de Plantão - Documento Oficial:
O que é? Consolidação de 24h de operações em documento oficial assinado.
Informações Incluídas:
✅ Data e hora de início/término
✅ Todas as movimentações do período
✅ Equipamentos enviados e recebidos
✅ Nomes dos responsáveis
✅ Assinaturas eletrônicas
✅ Timestamp oficial do sistema
Como Gerar:
1. Acesse aba "Passagem de Plantão"
2. Sistema consolida automaticamente os últimos 24h
3. Revise as informações
4. Clique "Exportar Termo PDF"
5. PDF baixa pronto para assinatura
Segurança: Garante rastreabilidade total entre turnos!`;
};
const gerarRespostaConsulta = (pergunta, contexto) => {
const p = window.normalizarTextoIA(pergunta);
const unidadePergunta = window.localizarUnidadeNaPergunta(pergunta);
if (p.includes('critic') && !unidadePergunta) {
return `⚠️ Consulta de Itens Críticos:
Posso verificar isso, mas preciso do escopo.
Deseja consultar:
• Central BH
• Um núcleo/subnúcleo/extensão específico
• O consolidado geral
Exemplos:
• "Tem algo crítico na Central BH?"
• "Tem algo crítico no Núcleo Uberaba?"
• "Tem algo crítico no Subnúcleo Divinópolis?"`;
}
let resposta = `📊 Consulta de Estoque - Dados Atuais: `;
resposta += `Resumo Geral:
• Total em Estoque: ${contexto.estatisticas.totalAtivos || 0}
• Tipos de Equipamentos: ${contexto.estatisticas.totalItens || 0}
• Em Trânsito: ${contexto.estatisticas.totalTransito || 0} remessas
• Pendente: ${contexto.estatisticas.totalPendente || 0} operações `;
try {
const baseEstoque = contexto.estoque.central || {};
const criticos = itensConfig
.filter(item => ((baseEstoque[item]?.atual || 0) <= (baseEstoque[item]?.baixo || 0)) && ((baseEstoque[item]?.baixo || 0) > 0))
.map(item => `🔴 ${window.formatItemName(item)}: ${baseEstoque[item]?.atual || 0} (Mín: ${baseEstoque[item]?.baixo || 0})`);
if (criticos.length > 0) {
resposta += `⚠️ Itens em Nível Crítico: ${criticos.join(' ')}
Ação Recomendada: Solicite suprimento pela aba "${window.obterNomeAbaExibicao('logistica')}"!`;
} else {
resposta += `✅ Nenhum item crítico! Sistema está saudável.`;
}
} catch (e) {
resposta += `✅ Estoque consultado com sucesso! `;
}
return resposta;
};
const gerarRespostaFallback = (pergunta, contexto, analise) => {
return `💡 Sobre o SIGME:
Sua pergunta: "${pergunta}"
Tópicos que posso ajudar:
✅ Como fazer envios e devoluções
✅ Preencher campos corretamente
✅ Resolver problemas técnicos
✅ Consultar estoque em tempo real
✅ Processos de auditoria
✅ Admissão e uso diário
Dica: Seja mais específico! Por exemplo:
• "Como preencho o MASP?"
• "Qual meu estoque atual?"
• "O sistema não abre"
• "Como bipar seriais?"
Perguntas Sugeridas para essa aba:
${(window.perguntasPorAba && window.perguntasPorAba[window.iaState.abaAtual])?.perguntas?.slice(0, 3).map(q => `• ${q}`).join(' ') || '• Visite a aba para ver sugestões'}`;
};
window.gerarRespostaProfissional = async (pergunta, historico = []) => {
const contexto = window.extrairContextoCompleto();
const analise = window.analisarIntencaoProfunda(pergunta, historico);
// STEP 1: Verificar se é continuação de conversa
if (analise.ehContinuacao && analise.contextoAnterior) {
const respostaContexto = analise.contextoAnterior.resposta;
return `${analise.temFrustacao ? '🤝 Vejo! Deixa eu ser mais claro: ' : ''}${respostaContexto.substring(0, 200)}... Para saber mais sobre esse tema, você gostaria de: A) Ver um exemplo prático? B) Saber qual é o próximo passo?`;
}
// STEP 2: Se está frustrado, mudar tom
let tonResposta = '';
if (analise.temFrustacao) {
tonResposta = `🤝 Entendo! Vou ir bem lentamente. Acompanhe comigo: `;
}
// STEP 3: Validar permissões se for operação
if (analise.intentos.length > 0) {
const operacao = analise.intentos[0] + '_' + (analise.intentos[1] || '').replace(/\s+/g, '_');
const precond = window.verificarPrecondoes(operacao, contexto);
if (!precond.valido) {
window.registrarPerguntaNaoProcessada(pergunta, 'VALIDACAO_FALHOU', precond.tipo);
return `${tonResposta}${precond.mensagem}`;
}
}
// STEP 4: Gerar resposta baseada em intenção
let resposta = '';
if (analise.ehPreenchimento) {
resposta = gerarRespostaPreenchimento(pergunta, contexto);
} else if (analise.ehProblema) {
resposta = gerarRespostaProblema(pergunta, contexto);
} else if (analise.ehValidacao) {
resposta = gerarRespostaValidacao(pergunta, contexto);
} else if (/(receb|dar entrada|entrada de equip|entrada em equip|confirmar chegada)/i.test(window.normalizarTextoIA(pergunta))) {
resposta = gerarRespostaRecebimento(pergunta, contexto);
} else if (analise.intentos.includes('envio')) {
resposta = gerarRespostaEnvio(pergunta, contexto);
} else if (analise.intentos.includes('devolucao')) {
resposta = gerarRespostaDevolucao(pergunta, contexto);
} else if (analise.intentos.includes('auditoria')) {
resposta = gerarRespostaAuditoria(pergunta, contexto);
} else if (analise.intentos.includes('admissao')) {
resposta = gerarRespostaAdmissao(pergunta, contexto);
} else if (analise.intentos.includes('spacecom')) {
resposta = gerarRespostaSpacecom(pergunta, contexto);
} else if (analise.intentos.includes('dashboard')) {
resposta = gerarRespostaDashboard(pergunta, contexto);
} else if (analise.intentos.includes('fluxo')) {
resposta = gerarRespostaFluxo(pergunta, contexto);
} else if (analise.intentos.includes('plantao')) {
resposta = gerarRespostaPlantao(pergunta, contexto);
} else if (analise.ehConsulta) {
resposta = gerarRespostaConsulta(pergunta, contexto);
} else {
resposta = gerarRespostaFallback(pergunta, contexto, analise);
window.registrarPerguntaNaoProcessada(pergunta, 'FALLBACK', 'INTENÇÃO_INDEFINIDA');
}
// STEP 5: Adicionar próximo passo sugerido
try {
const proximoPasso = window.sugerirProximoPasso(analise.intentos[0], 'pedido_suprimento');
if (proximoPasso) {
resposta += `⏭️ Próximo Passo: ${proximoPasso.descricao}Clique: "${proximoPasso.pergunta}" `;
}
} catch (e) {
console.log('Info: Próximo passo não disponível');
}
return window.normalizarRespostaIA(tonResposta + resposta);
};
// SISTEMA DE LOGGING E APRENDIZADO
window.registrarPerguntaNaoProcessada = (pergunta, categoria, tipo) => {
const registro = {
pergunta,
categoria,
tipo,
timestamp: new Date().toISOString(),
usuario: currentUser?.nome || 'Desconhecido',
aba: window.iaState.abaAtual
};
window.iaState.logPerguntas.push(registro);
console.log('📋 PERGUNTA NÃO PROCESSADA:', registro);
// Enviar para análise (opcional: backend)
// POST /api/ia-feedback com registro
};
// ATUALIZAR SUGESTÕES DINÂMICAS
// Função já definida em linha 1681 - removido duplicata
// ENVIAR PERGUNTA - ENTRY POINT PARA O USUÁRIO
window.enviarPerguntaIA = () => {
const input = $('input-ia');
const msg = input.value.trim();
if (!msg) return;
window.perguntarIA(msg);
input.value = '';
};
// PERGUNTA IA - PROCESSA PERGUNTA E GERA RESPOSTA PROFISSIONAL
window.perguntarIA = async (pergunta) => {
const chat = $('chat-ia-container');
window.monitorarAbas();
// Adicionar pergunta do usuário ao histórico
chat.innerHTML += ``;
try {
// Gerar resposta profissional com contexto
const resposta = await window.gerarRespostaProfissional(pergunta, window.iaState.historico);
// Adicionar ao histórico
window.iaState.historico.push({
pergunta,
resposta,
aba: window.iaState.abaAtual,
timestamp: new Date().toISOString(),
usuario: currentUser?.nome || 'Usuário'
});
// Manter histórico limitado (últimas 50 perguntas)
if (window.iaState.historico.length > 50) {
window.iaState.historico = window.iaState.historico.slice(-50);
}
setTimeout(() => {
chat.innerHTML += ``;
chat.scrollTop = chat.scrollHeight;
lucide.createIcons();
}, 600);
} catch (erro) {
console.error('Erro na IA:', erro);
window.registrarPerguntaNaoProcessada(pergunta, 'ERRO_SISTEMA', erro.message);
setTimeout(() => {
chat.innerHTML += `
❌ Erro ao processar: Reformule sua pergunta ou contate suporte
`;
chat.scrollTop = chat.scrollHeight;
}, 600);
}
chat.scrollTop = chat.scrollHeight;
lucide.createIcons();
// Atualizar sugestões
setTimeout(() => window.atualizarSugestoesPerguntas(), 300);
};
// ============================================================================
// FIM DO SISTEMA DE IA PROFISSIONAL - CÓDIGO LIMPO E FUNCIONAL
// ============================================================================
window.renderPainelExecutivo = () => {};
window.verificarEstoqueCriticoGlobal = () => {
if(currentUser.role === 'nucleo') return;
let listaCriticos = []; let arrayCriticos = [];
itensConfig.forEach(i => { const c = window.dbData.inventory[i]; if(c && c.atual <= c.baixo) { listaCriticos.push(`[Central BH] - ${i} (Atual: ${c.atual} / Mín: ${c.baixo})`); arrayCriticos.push({un: 'Central BH', it: i, at: c.atual, min: c.baixo}); } });
nucleosNames.forEach(n => { itensConfig.forEach(i => { if(window.dbData.nucleosInventory[n]) { const c = window.dbData.nucleosInventory[n][i]; if(c && c.atual <= c.baixo) { listaCriticos.push(`[${n}] - ${i} (Atual: ${c.atual} / Mín: ${c.baixo})`); arrayCriticos.push({un: n, it: i, at: c.atual, min: c.baixo}); } } }); });
const btnAlert = $('global-alert-btn'); const alertList = $('alert-list');
if(listaCriticos.length > 0) {
btnAlert.classList.remove('hidden'); window.relatorioCriticoString = listaCriticos.join("\n"); window.relatorioCriticoArray = arrayCriticos;
if(alertList) {
alertList.innerHTML = '';
arrayCriticos.forEach(c => {
alertList.innerHTML += ` ${c.un}${c.it}
Est: ${c.at} (Mín: ${c.min})
`;
});
}
}
else {
btnAlert.classList.add('hidden'); window.relatorioCriticoString = ""; window.relatorioCriticoArray = [];
if(alertList) alertList.innerHTML = '';
}
lucide.createIcons();
};
window.copiarAlertaWhatsApp = () => {
if(!window.relatorioCriticoString) return;
let txt = `🚨 *SIGME - ALERTA DE RUPTURA DE ESTOQUE* 🚨\n🗓️ ${new Date().toLocaleString('pt-BR')}\n\n`;
txt += window.relatorioCriticoString;
txt += `\n\n📌 _Por favor, providencie o suprimento via Painel de Gestão Logística._`;
navigator.clipboard.writeText(txt).then(() => { alert("Aviso copiado com sucesso!\n\nAgora é só abrir o WhatsApp, clicar com o botão direito e 'Colar' (ou Ctrl+V) para enviar para a equipe."); }).catch(() => { alert("Erro ao copiar o texto."); });
};
window.enviarEmailCritico = () => {
if(!window.relatorioCriticoString) return;
let emailText = `SISTEMA SIGME - ALERTA AUTOMÁTICO DE RUPTURA DE ESTOQUE\nData da Leitura: ${new Date().toLocaleString('pt-BR')}\n\n`;
emailText += `Atenção Equipe de Suporte,\nOs seguintes pólos e equipamentos atingiram o Nível Crítico (Abaixo da Margem de Segurança) e necessitam de reposição ou remanejamento urgente:\n\n`;
emailText += window.relatorioCriticoString;
emailText += `\n\nPor favor, providencie o suprimento via Painel de Gestão Logística.\nAtt,\nAgente Inteligente SIGME V13.`;
window.location.href = `mailto:suportedgme@gmail.com?subject=URGENTE: Ruptura de Estoque SIGME PPMG&body=${encodeURIComponent(emailText)}`;
};
window.abrirScanner = (inputId) => {
$('scanner-target-id').value = inputId; openModal('modal-scanner');
window.html5QrcodeScanner = new Html5QrcodeScanner("reader", { fps: 10, qrbox: {width: 250, height: 250} }, false);
window.html5QrcodeScanner.render((decodedText) => {
const targetId = $('scanner-target-id').value; const inputEl = $(targetId);
if(inputEl) { let currentVal = inputEl.value; inputEl.value = currentVal ? currentVal + "\n" + decodedText : decodedText; if(window.atualizarContagemSeriais) window.atualizarContagemSeriais(targetId); }
fecharScanner();
}, (error) => {});
};
window.fecharScanner = () => {
if(window.html5QrcodeScanner) { window.html5QrcodeScanner.clear().then(() => { closeModal('modal-scanner'); }).catch(e => { closeModal('modal-scanner'); }); } else { closeModal('modal-scanner'); }
};
function getStatusInfo(a, i, b, unitName = "") { if (unitName === "Admissão/NSI") return { corText: "text-status-ok", bgBadge: "bg-status-ok/20", borderCard: "border-status-ok", label: "OPERACIONAL", icon: "check-circle-2" }; if (a <= b) return { corText: "text-status-crit", bgBadge: "bg-status-crit/20", borderCard: "border-status-crit", label: "CRÍTICO", icon: "alert-triangle" }; if (a < i) return { corText: "text-status-warn", bgBadge: "bg-status-warn/20", borderCard: "border-status-warn", label: "ATENÇÃO", icon: "alert-circle" }; return { corText: "text-status-ok", bgBadge: "bg-status-ok/20", borderCard: "border-status-ok", label: "OPERACIONAL", icon: "check-circle-2" }; }
const nucleoAliases = {
"CENTRAL BH": "Central BH",
"SUBNUCLEO DE SAO JOAO DEL REY": "Subnúcleo de São João Del Rey",
"SUBNUCLEO DE SAO JOAO DEL REI": "Subnúcleo de São João Del Rey",
"SUBNUCLEO SJDR": "Subnúcleo de São João Del Rey",
"SAO JOAO DEL REY": "Subnúcleo de São João Del Rey",
"SAO JOAO DEL REI": "Subnúcleo de São João Del Rey",
"SJDR": "Subnúcleo de São João Del Rey",
["SUBNUCLEO DE ALMEN" + "AR"]: "Subnúcleo de Almenara",
"SUBNUCLEO DE ALMENARA": "Subnúcleo de Almenara",
["ALMEN" + "AR"]: "Subnúcleo de Almenara",
"ALMENARA": "Subnúcleo de Almenara"
};
window.normalizarUnidadeNucleo = (unidade) => {
if (!unidade) return unidade;
const texto = String(unidade).trim();
const chave = texto.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase();
return nucleoAliases[chave] || texto;
};
window.resolverChaveNucleo = (unidade) => {
const normalizada = window.normalizarUnidadeNucleo(unidade);
if (!normalizada) return normalizada;
if (normalizada === 'TODOS' || normalizada === 'Central BH') return normalizada;
if (window.dbData?.nucleosInventory?.[normalizada]) return normalizada;
const inventarioChaves = Object.keys(window.dbData?.nucleosInventory || {});
const alvo = String(normalizada).normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase();
const encontrada = inventarioChaves.find(chave => String(chave).normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase() === alvo);
return encontrada || normalizada;
};
window.obterChaveCanonicaUnidade = (unidade) => {
if (!unidade) return '';
const resolvida = (window.resolverChaveNucleo ? window.resolverChaveNucleo(unidade) : unidade) || unidade;
return String(resolvida)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
.toLowerCase();
};
window.hierarquiaUnidades = {
'Central BH': ['Admissão/NSI', 'Subnúcleo Divinópolis', 'Extensão Gameleira'],
'Núcleo de Juiz de Fora': ['Subnúcleo Muriaé', 'Subnúcleo Ubá', 'Subnúcleo de São João Del Rey'],
'Núcleo Uberaba': ['Subnúcleo Araxá'],
'Núcleo Montes Claros': ['Subnúcleo Taiobeiras'],
'Núcleo Curvelo': ['Subnúcleo Diamantina'],
'Núcleo de Teófilo Otoni': ['Subnúcleo de Almenara'],
'Núcleo Pouso Alegre': ['Subnúcleo Varginha', 'Extensão Três Corações'],
'Núcleo Poços de Caldas': ['Subnúcleo Alfenas', 'Subnúcleo Guaranésia']
};
window.obterUnidadesHierarquicasDeLista = (lista = []) => {
const pendentes = Array.isArray(lista) ? lista.slice() : [];
const visitadas = new Set();
while (pendentes.length) {
const atual = pendentes.shift();
const resolvida = window.resolverChaveNucleo ? window.resolverChaveNucleo(atual) : atual;
if (!resolvida) continue;
const chave = String(resolvida).trim();
if (!chave || visitadas.has(chave)) continue;
visitadas.add(chave);
const filhos = window.hierarquiaUnidades?.[chave] || [];
filhos.forEach(filho => pendentes.push(filho));
}
return Array.from(visitadas);
};
window.obterUnidadesHierarquicasUsuario = () => {
const base = window.obterUnidadesUsuario();
return window.obterUnidadesHierarquicasDeLista(base);
};
window.obterUnidadesHierarquicasUsuarioCanonicas = () => {
return window.obterUnidadesHierarquicasUsuario().map(u => window.obterChaveCanonicaUnidade(u)).filter(Boolean);
};
window.obterUnidadesPermitidasParaPedido = () => {
const role = String(currentUser?.role || '');
if (role === 'admin' || role === 'central') {
return ['Central BH', ...nucleosNames];
}
if (role === 'nucleo') {
return window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : (currentUser?.unidades || []);
}
const unidadesProprias = Array.isArray(currentUser?.unidades)
? currentUser.unidades
: (typeof currentUser?.unidades === 'string'
? currentUser.unidades.split(',').map(u => u.trim()).filter(Boolean)
: (currentUser?.unidade ? [currentUser.unidade] : []));
return unidadesProprias.length ? unidadesProprias : ['Central BH'];
};
window.unidadePertenceAoUsuario = (unidade, unidadesUsuarioCanonicas = []) => {
if (!Array.isArray(unidadesUsuarioCanonicas) || unidadesUsuarioCanonicas.length === 0) return false;
const permitidas = new Set(window.obterUnidadesHierarquicasDeLista(unidadesUsuarioCanonicas).map(u => window.obterChaveCanonicaUnidade(u)).filter(Boolean));
const chave = window.obterChaveCanonicaUnidade(unidade);
return permitidas.has(chave);
};
window.restaurarPedidosAtivosNaoArquivados = async function() {
const lista = Array.isArray(window.dbData?.transferencias) ? window.dbData.transferencias : [];
if (!lista.length) return;
const statusAtivos = new Set(['Pendente', 'Autorizado', 'Em Preparação', 'Pronto para Envio', 'Recibo', 'Em Trânsito']);
let alterou = false;
lista.forEach(t => {
if (!t) return;
if (String(t.destino || '').trim() === 'Central BH') return;
const st = String(t.status || '').trim();
if (statusAtivos.has(st) && t.arquivado === true) {
t.arquivado = false;
alterou = true;
}
});
if (!alterou) return;
await saveToCloud(['transferencias']);
};
window.salvarBackupTransferenciasLocal = function(lista = []) {
try {
if (!Array.isArray(lista) || lista.length === 0) return;
const payload = { savedAt: new Date().toISOString(), data: lista };
localStorage.setItem('sigme_transferencias_backup', JSON.stringify(payload));
const rawHist = localStorage.getItem('sigme_transferencias_backup_history');
const hist = Array.isArray(JSON.parse(rawHist || '[]')) ? JSON.parse(rawHist || '[]') : [];
hist.push(payload);
if (hist.length > 20) hist.splice(0, hist.length - 20);
localStorage.setItem('sigme_transferencias_backup_history', JSON.stringify(hist));
} catch (_) {}
};
window.removerIdDoBackupLocal = function(idRemover) {
try {
if (!idRemover) return;
const chave = String(idRemover).trim();
const raw = localStorage.getItem('sigme_transferencias_backup');
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed?.data)) {
parsed.data = parsed.data.filter(t => String(t?.id || '').trim() !== chave);
localStorage.setItem('sigme_transferencias_backup', JSON.stringify(parsed));
}
}
const rawHist = localStorage.getItem('sigme_transferencias_backup_history');
if (rawHist) {
const hist = JSON.parse(rawHist);
if (Array.isArray(hist)) {
hist.forEach(snap => {
if (Array.isArray(snap?.data)) {
snap.data = snap.data.filter(t => String(t?.id || '').trim() !== chave);
}
});
localStorage.setItem('sigme_transferencias_backup_history', JSON.stringify(hist));
}
}
} catch (_) {}
};
window.carregarBackupTransferenciasLocal = function() {
try {
const raw = localStorage.getItem('sigme_transferencias_backup');
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed?.data) ? parsed.data : [];
} catch (_) {
return [];
}
};
window.carregarHistoricoBackupTransferenciasLocal = function() {
try {
const raw = localStorage.getItem('sigme_transferencias_backup_history');
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch (_) {
return [];
}
};
window.salvarBackupAdmissoesLocal = function(lista = []) {
try {
if (!Array.isArray(lista) || lista.length === 0) return;
const payload = { savedAt: new Date().toISOString(), data: lista };
localStorage.setItem('sigme_admissoes_backup', JSON.stringify(payload));
const rawHist = localStorage.getItem('sigme_admissoes_backup_history');
const hist = Array.isArray(JSON.parse(rawHist || '[]')) ? JSON.parse(rawHist || '[]') : [];
hist.push(payload);
if (hist.length > 20) hist.splice(0, hist.length - 20);
localStorage.setItem('sigme_admissoes_backup_history', JSON.stringify(hist));
} catch (_) {}
};
window.carregarBackupAdmissoesLocal = function() {
try {
const raw = localStorage.getItem('sigme_admissoes_backup');
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed?.data) ? parsed.data : [];
} catch (_) {
return [];
}
};
window._recuperacaoAdmissoesEmAndamento = false;
window.tentarRecuperarAdmissoesSeEsvaziar = async function(listaAtual = []) {
try {
if (window._recuperacaoAdmissoesEmAndamento) return false;
if (!Array.isArray(listaAtual) || listaAtual.length > 0) return false;
if (!window.currentUser || !['admin', 'central'].includes(String(window.currentUser.role || ''))) return false;
const backup = window.carregarBackupAdmissoesLocal ? window.carregarBackupAdmissoesLocal() : [];
if (!Array.isArray(backup) || backup.length === 0) return false;
const confirmar = confirm(
'ALERTA CRÍTICO: a nuvem retornou o histórico de admissão vazio.\n\n' +
'Deseja restaurar automaticamente usando o backup local deste navegador?'
);
if (!confirmar) return false;
window._recuperacaoAdmissoesEmAndamento = true;
window._allowUnsafeAdmissoesSave = true;
window.dbData.admissoes = backup;
await saveToCloud(['admissoes']);
window.showToast('Histórico de admissão restaurado automaticamente do backup local.', 'success');
return true;
} catch (err) {
console.error('Falha ao recuperar admissões do backup local:', err);
return false;
} finally {
window._allowUnsafeAdmissoesSave = false;
window._recuperacaoAdmissoesEmAndamento = false;
}
};
window._recuperacaoTransferenciasEmAndamento = false;
window.tentarRecuperarTransferenciasSeEsvaziar = async function(listaAtual = []) {
try {
if (window._recuperacaoTransferenciasEmAndamento) return false;
if (!Array.isArray(listaAtual) || listaAtual.length > 0) return false;
if (!window.currentUser || !['admin', 'central'].includes(String(window.currentUser.role || ''))) return false;
const historicoLocal = window.obterListaTransferenciasMescladaHistoricoLocal
? window.obterListaTransferenciasMescladaHistoricoLocal({ incluirSomenteIdsAtuais: false })
: [];
if (!Array.isArray(historicoLocal) || historicoLocal.length === 0) return false;
const confirmar = confirm(
'ALERTA CRÍTICO: a nuvem retornou o histórico de transferências vazio.\n\n' +
'Deseja restaurar automaticamente usando o backup local deste navegador?'
);
if (!confirmar) return false;
window._recuperacaoTransferenciasEmAndamento = true;
window.dbData.transferencias = historicoLocal;
await saveToCloud(['transferencias']);
window.showToast('Histórico de transferências restaurado automaticamente do backup local.', 'success');
return true;
} catch (err) {
console.error('Falha ao recuperar transferências do backup local:', err);
return false;
} finally {
window._recuperacaoTransferenciasEmAndamento = false;
}
};
window.isRegistroRecuperacaoPlaceholder = function(registro) {
if (!registro) return false;
return String(registro?.registradoMasp || '').trim() === 'RECOVERY'
|| String(registro?.observacoesSolicitante || '').includes('Registro reidratado automaticamente para recuperação de rastreio crítico');
};
window.limparRegistroRecuperacaoPlaceholder = function(registro) {
if (!window.isRegistroRecuperacaoPlaceholder(registro)) return registro;
const clone = JSON.parse(JSON.stringify(registro || {}));
delete clone.registradoMasp;
delete clone.observacoesSolicitante;
if (String(clone.status || '').trim() === 'Em Preparação' && !clone.itens?.length && !Object.keys(clone.seriais || {}).length) {
clone.status = 'Pendente';
}
return clone;
};
window.sanitizarListaTransferenciasRecuperacao = function(lista = []) {
if (!Array.isArray(lista)) return [];
return lista.map(registro => window.limparRegistroRecuperacaoPlaceholder(registro));
};
window.calcularCompletudeTransferencia = function(t) {
if (!t) return -1;
let score = 0;
const itens = Array.isArray(t.itens) ? t.itens : [];
const seriaisObj = t.seriais && typeof t.seriais === 'object' ? t.seriais : {};
const seriaisCount = Object.values(seriaisObj).reduce((acc, arr) => acc + (Array.isArray(arr) ? arr.length : 0), 0);
if (itens.length > 0) score += 3;
if (seriaisCount > 0) score += 4;
if (t.transportador) score += 1;
if (t.aceiteData || t.recebimentoData) score += 1;
if (t.solicitante || t.registradoPor) score += 1;
if (window.isRegistroRecuperacaoPlaceholder(t)) score -= 5;
return score;
};
window.obterMelhorRegistroTransferenciaPorId = function(id, listaAtual = []) {
const chave = String(id || '').trim();
if (!chave) return null;
const candidatos = [];
const add = (registro) => {
if (!registro) return;
if (String(registro?.id || '').trim() !== chave) return;
candidatos.push(registro);
};
(Array.isArray(listaAtual) ? listaAtual : []).forEach(add);
(window.carregarBackupTransferenciasLocal() || []).forEach(add);
const hist = window.carregarHistoricoBackupTransferenciasLocal();
hist.forEach(snap => {
const arr = Array.isArray(snap?.data) ? snap.data : [];
arr.forEach(add);
});
if (!candidatos.length) return null;
candidatos.sort((a, b) => window.calcularCompletudeTransferencia(b) - window.calcularCompletudeTransferencia(a));
return candidatos[0] ? JSON.parse(JSON.stringify(candidatos[0])) : null;
};
window.restaurarTransferenciasComHistoricoLocal = async function() {
if (window._restaurarTransferenciasHistoricoExecutando) return false;
const restauradas = window.obterListaTransferenciasMescladaHistoricoLocal({ incluirSomenteIdsAtuais: false });
const listaAtual = Array.isArray(window.dbData?.transferencias) ? window.dbData.transferencias : [];
const mudouQuantidade = restauradas.length !== listaAtual.length;
const mudouConteudo = !mudouQuantidade && JSON.stringify(restauradas) !== JSON.stringify(listaAtual);
if (!mudouQuantidade && !mudouConteudo) return false;
window._restaurarTransferenciasHistoricoExecutando = true;
try {
window.dbData.transferencias = restauradas;
await saveToCloud(['transferencias']);
return true;
} finally {
window._restaurarTransferenciasHistoricoExecutando = false;
}
};
window.obterListaTransferenciasMescladaHistoricoLocal = function(options = {}) {
const incluirSomenteIdsAtuais = options?.incluirSomenteIdsAtuais !== false;
const listaAtual = Array.isArray(window.dbData?.transferencias) ? window.dbData.transferencias : [];
const backupAtual = window.carregarBackupTransferenciasLocal();
const historicoBackup = window.carregarHistoricoBackupTransferenciasLocal();
const mapa = new Map();
const idsAtuais = new Set();
const incluirAtual = (registro) => {
if (!registro) return;
const chave = String(registro?.id || '').trim();
if (!chave) return;
idsAtuais.add(chave);
const base = window.limparRegistroRecuperacaoPlaceholder(registro);
mapa.set(chave, JSON.parse(JSON.stringify(base)));
};
const incluirHistorico = (registro) => {
if (!registro) return;
const chave = String(registro?.id || '').trim();
if (!chave) return;
if (incluirSomenteIdsAtuais && !idsAtuais.has(chave)) return;
if (mapa.has(chave)) return;
const base = window.limparRegistroRecuperacaoPlaceholder(registro);
const existente = mapa.get(chave);
if (!existente || window.calcularCompletudeTransferencia(base) > window.calcularCompletudeTransferencia(existente)) {
mapa.set(chave, JSON.parse(JSON.stringify(base)));
}
};
listaAtual.forEach(incluirAtual);
backupAtual.forEach(incluirHistorico);
historicoBackup.forEach(snap => (Array.isArray(snap?.data) ? snap.data : []).forEach(incluirHistorico));
return Array.from(mapa.values());
};
window.PEDIDOS_CRITICOS_FIXOS = ['DME-NCL-382171824'];
window._resgatePedidosCriticosExecutando = false;
window.resgatarPedidosCriticos = async function(ids = window.PEDIDOS_CRITICOS_FIXOS) {
if (window._resgatePedidosCriticosExecutando) return;
const lista = Array.isArray(window.dbData?.transferencias) ? window.dbData.transferencias : [];
if (!Array.isArray(ids) || ids.length === 0 || !lista) return;
window._resgatePedidosCriticosExecutando = true;
try {
let alterou = false;
ids.forEach(id => {
const chave = String(id || '').trim();
if (!chave) return;
const existente = lista.find(t => String(t?.id || '').trim() === chave);
if (existente) {
const melhor = window.obterMelhorRegistroTransferenciaPorId(chave, lista);
if (melhor && window.calcularCompletudeTransferencia(melhor) > window.calcularCompletudeTransferencia(existente)) {
Object.keys(existente).forEach(k => delete existente[k]);
Object.assign(existente, { ...melhor, arquivado: false });
alterou = true;
}
const limpo = window.limparRegistroRecuperacaoPlaceholder(existente);
if (limpo !== existente) {
Object.keys(existente).forEach(k => delete existente[k]);
Object.assign(existente, limpo);
alterou = true;
}
if (existente.arquivado === true) {
existente.arquivado = false;
alterou = true;
}
if (!existente.status) {
existente.status = 'Em Preparação';
alterou = true;
}
return;
}
const melhorAusente = window.obterMelhorRegistroTransferenciaPorId(chave, lista);
if (melhorAusente) {
lista.push({ ...window.limparRegistroRecuperacaoPlaceholder(melhorAusente), arquivado: false });
alterou = true;
}
});
if (alterou) {
await saveToCloud(['transferencias']);
}
} finally {
window._resgatePedidosCriticosExecutando = false;
}
};
window.obterOuCriarInventarioNucleo = (nucleo) => {
if (!window.dbData.nucleosInventory[nucleo]) {
window.dbData.nucleosInventory[nucleo] = {};
itensConfig.forEach(item => {
window.dbData.nucleosInventory[nucleo][item] = { atual: 0, ideal: 50, baixo: 15 };
});
}
return window.dbData.nucleosInventory[nucleo];
};
window.getUltimaMovimentacao = (unidade, itemKey) => {
let ultimaData = null; let tipoMov = null;
const parse = (dStr) => {
if (!dStr) return null;
let clean = dStr.replace(',', '').replace(/\s+/g, ' ').trim();
let parts = clean.split(' ');
let dPart = parts[0]; let tPart = parts[1] || '00:00:00';
if (dPart.includes('/')) {
let p = dPart.split('/');
if (p.length === 3) return new Date(`${p[2].replace(/\D/g,'')}-${p[1]}-${p[0]}T${tPart}`);
}
if (dPart.includes('-')) return new Date(`${dPart}T${tPart}`);
return null;
};
const update = (dStr, type) => {
const dt = parse(dStr);
if (dt && (!ultimaData || dt > ultimaData)) { ultimaData = dt; tipoMov = type; }
};
const unidadeResolvida = window.resolverChaveNucleo(unidade);
const units = unidadeResolvida === "TODOS" ? ['Central BH', ...nucleosNames] : [unidadeResolvida];
(window.dbData.transferencias || []).forEach(t => {
const involves = (t.itens ? t.itens.some(i => i.item === itemKey) : t.item === itemKey);
if (involves) {
if (units.includes(t.destino)) { if (t.status === 'Concluído') update(t.aceiteData || t.data, 'in'); else update(t.data, 'in'); }
if (units.includes(t.origem)) { update(t.data, 'out'); if (t.transportador?.dataHora) update(t.transportador.dataHora, 'out'); }
}
});
(window.dbData.admissoes || []).forEach(a => {
const involves = (a.itens ? a.itens.some(i => i.item === itemKey) : a.item === itemKey);
if (involves && units.includes(window.resolverChaveNucleo(a.setor))) update(a.data, 'out');
});
(window.dbData.movimentosManuais || []).forEach(m => {
if (m.item === itemKey) {
let match = false;
if (units.includes('Central BH')) { if (m.obs.includes('Central BH') || m.tipo.includes('Sede') || m.tipo.includes('Entrada') || m.tipo.includes('Saída')) match = true; }
if (!match) { for (let u of units) { if (u !== 'Central BH' && m.obs.includes(u)) { match = true; break; } } }
if (match) {
let type = m.tipo.includes('Entrada') ? 'in' : 'out';
if (m.obs.includes('Valor corrigido de')) { const vals = m.obs.match(/corrigido de (\d+) para (\d+)/); if (vals) type = parseInt(vals[2]) > parseInt(vals[1]) ? 'in' : 'out'; }
update(m.data, type);
}
}
});
if (!ultimaData) return 'Sem registro';
const dStr = ultimaData.toLocaleString('pt-BR').substring(0, 16);
if (tipoMov === 'in') return ` ENT: ${dStr} `;
if (tipoMov === 'out') return ` SAI: ${dStr} `;
return dStr;
};
window._filtroEstoqueHistorico = window._filtroEstoqueHistorico || {
central: '',
interior: ''
};
window.parseDataHoraSigme = function(dataTexto) {
if (!dataTexto) return null;
const raw = String(dataTexto).trim().replace(',', ' ');
const br = raw.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
if (br) {
const dd = parseInt(br[1], 10);
const mm = parseInt(br[2], 10);
const yyyy = parseInt(br[3], 10);
const hh = parseInt(br[4] || '0', 10);
const mi = parseInt(br[5] || '0', 10);
const ss = parseInt(br[6] || '0', 10);
const dt = new Date(yyyy, mm - 1, dd, hh, mi, ss);
return Number.isNaN(dt.getTime()) ? null : dt;
}
const iso = raw.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
if (iso) {
const yyyy = parseInt(iso[1], 10);
const mm = parseInt(iso[2], 10);
const dd = parseInt(iso[3], 10);
const hh = parseInt(iso[4] || '0', 10);
const mi = parseInt(iso[5] || '0', 10);
const ss = parseInt(iso[6] || '0', 10);
const dt = new Date(yyyy, mm - 1, dd, hh, mi, ss);
return Number.isNaN(dt.getTime()) ? null : dt;
}
const dt = new Date(raw);
return Number.isNaN(dt.getTime()) ? null : dt;
};
window.formatarDateTimeLocal = function(dt) {
if (!(dt instanceof Date) || Number.isNaN(dt.getTime())) return '';
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}T${String(dt.getHours()).padStart(2, '0')}:${String(dt.getMinutes()).padStart(2, '0')}`;
};
window.obterInventarioSnapshotPorDataHora = function(unidadeAlvo, dataHoraIso) {
const resolvedUnit = unidadeAlvo === 'TODOS' ? 'TODOS' : window.resolverChaveNucleo(unidadeAlvo);
const filtroAtivo = Boolean(String(dataHoraIso || '').trim());
const cloneBase = (src = {}, itemNome = '') => ({
atual: parseInt(src.atual || 0, 10) || 0,
ideal: parseInt(src.ideal || 0, 10) || 0,
baixo: parseInt(src.baixo || 0, 10) || 0,
nome: itemNome || src.nome || ''
});
let snap = {};
if (resolvedUnit === 'Central BH') {
itensConfig.forEach(item => {
snap[item] = cloneBase(window.dbData.inventory?.[item] || {}, item);
});
} else if (resolvedUnit === 'TODOS') {
itensConfig.forEach(item => {
snap[item] = { atual: 0, ideal: 0, baixo: 0, nome: item };
});
const list = (currentUser.role !== 'nucleo') ? nucleosNames : (currentUser.unidades || []);
list.forEach(un => {
const key = window.resolverChaveNucleo(un);
itensConfig.forEach(item => {
const src = window.dbData.nucleosInventory?.[key]?.[item];
if (!src) return;
snap[item].atual += parseInt(src.atual || 0, 10) || 0;
snap[item].ideal += parseInt(src.ideal || 0, 10) || 0;
snap[item].baixo += parseInt(src.baixo || 0, 10) || 0;
});
});
} else {
itensConfig.forEach(item => {
snap[item] = cloneBase(window.dbData.nucleosInventory?.[resolvedUnit]?.[item] || {}, item);
});
}
if (!filtroAtivo) {
return {
inventario: snap,
filtroAtivo: false,
dataRefTexto: 'agora'
};
}
const cutoff = window.parseDataHoraSigme(dataHoraIso);
if (!cutoff) {
return {
inventario: snap,
filtroAtivo: false,
dataRefTexto: 'agora'
};
}
const cutoffTs = cutoff.getTime();
const setAlvo = new Set();
if (resolvedUnit === 'TODOS') {
const list = (currentUser.role !== 'nucleo') ? nucleosNames : (currentUser.unidades || []);
list.forEach(u => setAlvo.add(window.resolverChaveNucleo(u)));
} else {
setAlvo.add(resolvedUnit);
}
const ajustar = (unidadeEvento, item, delta) => {
if (!item || !itensConfig.includes(item)) return;
const un = unidadeEvento === 'TODOS' ? 'TODOS' : window.resolverChaveNucleo(unidadeEvento);
if (!setAlvo.has(un)) return;
if (!snap[item]) snap[item] = { atual: 0, ideal: 0, baixo: 0, nome: item };
snap[item].atual += delta;
};
(window.dbData.admissoes || []).forEach(ad => {
const dt = window.parseDataHoraSigme(ad?.data);
if (!dt || dt.getTime() <= cutoffTs) return;
const fator = String(ad?.operacao || 'ENTREGA').toUpperCase() === 'RETORNO' ? 1 : -1;
(ad.itens || []).forEach(it => {
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (qtd <= 0) return;
ajustar(ad.setor, it.item, -(fator * qtd));
});
});
(window.dbData.transferencias || []).forEach(t => {
if (!t) return;
const status = String(t.status || '').trim();
const baixaAplicada = t.estoqueBaixado === true || ['Pronto para Envio', 'Recibo', 'Em Trânsito', 'Concluído'].includes(status);
const dtSaida = window.parseDataHoraSigme(t.transportador?.dataHora || t.dataHoraAnalise || t.dataHoraRegistro || t.data);
if (baixaAplicada && dtSaida && dtSaida.getTime() > cutoffTs) {
(t.itens || []).forEach(it => {
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (qtd <= 0) return;
ajustar(t.origem, it.item, qtd);
});
}
const dtEntrada = window.parseDataHoraSigme(t.aceiteData || t.recebimentoData || t.transportador?.dataHora || t.dataHoraAnalise || t.data);
if (status === 'Concluído' && dtEntrada && dtEntrada.getTime() > cutoffTs) {
(t.itens || []).forEach(it => {
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (qtd <= 0) return;
ajustar(t.destino, it.item, -qtd);
});
}
});
(window.dbData.movimentosManuais || []).forEach(m => {
if (!m || !m.item || m.item === 'MULTI') return;
const dt = window.parseDataHoraSigme(m.data);
if (!dt || dt.getTime() <= cutoffTs) return;
const qtd = parseInt(m.qtd || 0, 10) || 0;
if (qtd <= 0) return;
const tipo = String(m.tipo || '');
let sinal = 0;
if (/entrada|retorno/i.test(tipo)) sinal = 1;
else if (/saida|saída|envio|devolu/i.test(tipo)) sinal = -1;
if (!sinal) return;
const unidadeEvento = m.destinoResolvido || m.destino || m.unidade || 'Central BH';
ajustar(unidadeEvento, m.item, -(sinal * qtd));
});
itensConfig.forEach(item => {
if (!snap[item]) return;
if (snap[item].atual < 0) snap[item].atual = 0;
});
return {
inventario: snap,
filtroAtivo: true,
dataRefTexto: cutoff.toLocaleString('pt-BR')
};
};
window.renderFiltroEstoqueHistorico = function(tipoAba, onChange) {
const isCentral = tipoAba === 'central';
const root = isCentral ? $('dashboard') : $('nucleos');
if (!root) return;
const tituloWrap = root.querySelector('.mb-5.border-b.border-theme-border.pb-3');
if (!tituloWrap) return;
const boxId = isCentral ? 'filtro-estoque-historico-central' : 'filtro-estoque-historico-interior';
let box = $(boxId);
if (!box) {
box = document.createElement('div');
box.id = boxId;
box.className = 'w-full mt-3 p-3 rounded-2xl border border-theme-border bg-theme-body/40';
box.innerHTML = `
`;
tituloWrap.parentNode.insertBefore(box, tituloWrap.nextSibling);
}
const input = $(`${boxId}-dt`);
const status = $(`${boxId}-status`);
const key = isCentral ? 'central' : 'interior';
if (input) {
input.value = window._filtroEstoqueHistorico[key] || '';
}
if (status) {
status.textContent = window._filtroEstoqueHistorico[key]
? `Exibindo estoque historico em: ${window.parseDataHoraSigme(window._filtroEstoqueHistorico[key])?.toLocaleString('pt-BR') || 'data invalida'}`
: 'Exibindo estoque atual (tempo real).';
}
const btnAplicar = $(`${boxId}-aplicar`);
if (btnAplicar) btnAplicar.onclick = () => {
const val = input?.value || '';
window._filtroEstoqueHistorico[key] = val;
onChange();
};
const btnAgora = $(`${boxId}-agora`);
if (btnAgora) btnAgora.onclick = () => {
window._filtroEstoqueHistorico[key] = '';
if (input) input.value = '';
onChange();
};
const btnOntem = $(`${boxId}-ontem`);
if (btnOntem) btnOntem.onclick = () => {
const d = new Date();
d.setDate(d.getDate() - 1);
d.setHours(6, 0, 0, 0);
const val = window.formatarDateTimeLocal(d);
window._filtroEstoqueHistorico[key] = val;
if (input) input.value = val;
onChange();
};
if (window.lucide) lucide.createIcons();
};
window.renderDashboard = function() {
window.renderFiltroEstoqueHistorico('central', () => window.renderDashboard());
const grid = $('inventory-grid');
if(!grid) return;
grid.innerHTML = '';
const snapPack = window.obterInventarioSnapshotPorDataHora('Central BH', window._filtroEstoqueHistorico.central);
const estoqueRef = snapPack.inventario || {};
const iconMap = { "TZPR": "radio", "UPR": "router", "FONTE": "plug-2", "CM": "battery-medium", "CINTAS": "link", "TRAVAS": "lock-keyhole" };
const iconMapExtended = { ...iconMap, "MANUAL_MONIT": "book-open", "MANUAL_VITIMA": "book-open", "MANUAL_APP": "smartphone" };
itensConfig.forEach(key => {
const item = estoqueRef[key];
if(!item) return;
const st = getStatusInfo(item.atual, item.ideal, item.baixo, "Central BH");
const ic = iconMapExtended[key] || "box"; const perc = Math.min((item.ideal > 0 ? (item.atual / item.ideal) * 100 : 0), 100);
const pulseClass = st.label === 'CRÍTICO' ? 'alert-pulse' : ''; // Mantém o pulse para crítico
const podeAjustar = !snapPack.filtroAtivo;
const classeClick = podeAjustar ? 'cursor-pointer hover:text-gold-500 transition-colors' : 'opacity-95';
const clickAttr = podeAjustar ? `onclick="ajustarEstoqueManual('${key}', 'Central BH')" title="Clique para ajuste manual"` : 'title="Modo histórico: edição desabilitada"';
grid.innerHTML += `
${window.formatItemName(item.nome)} ${st.label}
${(item.atual || 0).toLocaleString('pt-BR')}
Ruptura: ${item.baixo} Meta: ${item.ideal}
ÚLTIMA MOV: ${window.getUltimaMovimentacao('Central BH', key)}
`;
});
const statusFiltro = $('filtro-estoque-historico-central-status');
if (statusFiltro) {
statusFiltro.textContent = snapPack.filtroAtivo
? `Exibindo estoque historico em: ${snapPack.dataRefTexto}`
: 'Exibindo estoque atual (tempo real).';
}
lucide.createIcons();
};
window.getDiasOciosos = function(nucleo, itemKey) {
if (!window.dbData.admissoes && !window.dbData.transferencias) return 999;
nucleo = window.resolverChaveNucleo(nucleo);
let ultimaData = null;
const checkDate = (dStr) => {
let dt;
if (dStr.includes('-')) { dt = new Date(dStr.split(' ')[0] + "T00:00:00"); }
else { let parts = dStr.split(' ')[0].split('/'); if(parts.length === 3) dt = new Date(`${parts[2]}-${parts[1]}-${parts[0]}T00:00:00`); }
if (dt && (!ultimaData || dt > ultimaData)) ultimaData = dt;
};
(window.dbData.admissoes || []).forEach(a => {
if (window.resolverChaveNucleo(a.setor) === nucleo) { let temItem = a.itens ? a.itens.some(i => i.item === itemKey && i.qtd > 0) : (a.item === itemKey && a.qtd > 0); if (temItem) checkDate(a.data); }
});
(window.dbData.transferencias || []).forEach(t => {
if (window.resolverChaveNucleo(t.origem) === nucleo && (t.status === 'Em Trânsito' || t.status === 'Recibo' || t.status === 'Concluído')) { let temItem = t.itens ? t.itens.some(i => i.item === itemKey && i.qtd > 0) : (t.item === itemKey && t.qtd > 0); if (temItem) checkDate(t.data); }
});
if (!ultimaData) return 999;
const diffTime = Math.abs(new Date() - ultimaData);
return Math.floor(diffTime / (1000 * 60 * 60 * 24));
};
window.renderNucleoDashboard = function() {
window.renderFiltroEstoqueHistorico('interior', () => window.renderNucleoDashboard());
const grid = $('nucleo-grid');
const title = $('nucleo-title');
if(!grid) return;
grid.innerHTML = '';
const unidadesHierarquicas = (currentUser.role === 'nucleo') ? (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : (currentUser.unidades || [])) : [];
let nuc = (currentUser.role === 'nucleo' && unidadesHierarquicas.length === 1) ? unidadesHierarquicas[0] : $('nucleo-select').value;
nuc = window.resolverChaveNucleo(nuc);
const isAdmin = (currentUser.role === 'admin' || currentUser.role === 'central');
const iconMap = { "TZPR": "radio", "UPR": "router", "FONTE": "plug-2", "CM": "battery-medium", "CINTAS": "link", "TRAVAS": "lock-keyhole", "MANUAL_MONIT": "book-open", "MANUAL_VITIMA": "book-open", "MANUAL_APP": "smartphone" };
if (title) {
title.innerText = nuc === 'TODOS' ? 'TODOS NÚCLEOS/SUBNÚCLEOS' : nuc.toUpperCase();
}
const snapPack = window.obterInventarioSnapshotPorDataHora(nuc, window._filtroEstoqueHistorico.interior);
const soma = snapPack.inventario || {};
itensConfig.forEach(key => {
const item = soma[key] || { atual: 0, ideal: 0, baixo: 0, nome: key };
const st = getStatusInfo(item.atual, item.ideal, item.baixo);
const bClass = isAdmin ? `border-t-8 ${st.borderCard}` : 'border-t-8 border-theme-border';
const icColor = isAdmin ? st.corText : 'text-theme-muted';
const perc = Math.min((item.ideal > 0 ? (item.atual / item.ideal) * 100 : 0), 100);
const ic = iconMap[item.nome] || "box";
const pulseClass = (isAdmin && st.label === 'CRÍTICO' && nuc !== "Admissão/NSI") ? 'alert-pulse' : '';
const podeAjustar = !snapPack.filtroAtivo && isAdmin && nuc !== 'TODOS';
let qtyHtml = `${(item.atual || 0).toLocaleString('pt-BR')}
`;
let ociosoHtml = '';
if (!snapPack.filtroAtivo && (key === 'TZPR' || key === 'UPR') && item.atual > 0 && nuc !== 'TODOS') {
let dias = window.getDiasOciosos(nuc, key);
if (dias >= 30) {
let txtDias = dias >= 999 ? '+30 dias' : `${dias} dias`;
ociosoHtml = `OCIO: ${txtDias} s/ uso
`;
}
}
grid.innerHTML += `${window.formatItemName(item.nome)}
${qtyHtml}${isAdmin ? `
${st.label}
Crítico: ${item.baixo} Meta: ${item.ideal}
` : ''}
ÚLTIMA MOV: ${window.getUltimaMovimentacao(nuc, key)}
${ociosoHtml}
`;
});
const statusFiltro = $('filtro-estoque-historico-interior-status');
if (statusFiltro) {
statusFiltro.textContent = snapPack.filtroAtivo
? `Exibindo estoque historico em: ${snapPack.dataRefTexto}`
: 'Exibindo estoque atual (tempo real).';
}
lucide.createIcons();
};
window.ajustarEstoqueManual = (itemKey, nucleo) => {
if(currentUser.role !== 'admin' && currentUser.role !== 'central') { alert("Acesso negado. Apenas gestão possui autorização para forçar atualizações manuais."); return; }
nucleo = window.resolverChaveNucleo(nucleo);
let valorAtual = 0;
if(nucleo === 'Central BH') { valorAtual = window.dbData.inventory[itemKey].atual; }
else { const inventarioNucleo = window.obterOuCriarInventarioNucleo(nucleo); if(!inventarioNucleo[itemKey]) inventarioNucleo[itemKey] = { atual: 0, ideal: 50, baixo: 15 }; valorAtual = inventarioNucleo[itemKey].atual; }
let novoValorStr = prompt(`Ajuste manual para [ ${itemKey} ] em [ ${nucleo} ]\n\nValor atual no sistema: ${valorAtual}\nDigite a quantidade EXATA (absoluta) que encontra-se na prateleira física hoje:`, valorAtual);
if(novoValorStr !== null && novoValorStr.trim() !== "") {
let novoValor = parseInt(novoValorStr, 10);
if(!isNaN(novoValor) && novoValor >= 0) {
let dif = novoValor - valorAtual;
if(dif !== 0) {
if(nucleo === 'Central BH') { window.dbData.inventory[itemKey].atual = novoValor; }
else { window.obterOuCriarInventarioNucleo(nucleo)[itemKey].atual = novoValor; }
window.dbData.movimentosManuais.push({
id: "ADJ-" + Date.now().toString().slice(-4) + Math.floor(Math.random()*100),
data: new Date().toLocaleString('pt-BR'),
item: itemKey,
tipo: dif > 0 ? 'Sede: Entrada Manual/Ajuste' : 'Sede: Saída Manual/Ajuste',
qtd: Math.abs(dif),
obs: `Ajuste de inventário forçado pelo card (${nucleo}). Valor corrigido de ${valorAtual} para ${novoValor}.`,
responsavel: currentUser.nome,
seriais: '',
serialsBipados: ''
});
saveToCloud(['inventory', 'nucleosInventory', 'movimentosManuais']).then(() => {
if(nucleo === 'Central BH') renderDashboard(); else renderNucleoDashboard(); window.renderPainelExecutivo(); window.renderGraficosAnalytics();
});
}
} else { alert("Valor inválido. Por favor, insira um número inteiro não negativo."); }
}
};
window.zerarPedidos = () => {
if(currentUser.role !== 'admin') return;
if(confirm("ATENÇÃO: Deseja apagar DEFINITIVAMENTE todos os pedidos e transferências da aba Envios?\n(As devoluções para a Central BH serão mantidas).")) {
const tokenConfirmacao = prompt('Digite EXATAMENTE: ZERAR ENVIOS');
if (tokenConfirmacao !== 'ZERAR ENVIOS') {
alert('Operação cancelada. Texto de confirmação inválido.');
return;
}
window.dbData.transferencias = (window.dbData.transferencias || []).filter(t => t.destino === 'Central BH');
window._allowUnsafeTransferenciasSave = true;
saveToCloud(['transferencias']).then(() => {
renderKanban('suprimento');
alert("Todos os pedidos de envio foram removidos com sucesso.");
}).finally(() => {
window._allowUnsafeTransferenciasSave = false;
});
}
};
window.apagarTransferencia = (id) => {
if(currentUser.role !== 'admin' && currentUser.role !== 'central') return;
const idx = window.dbData.transferencias.findIndex(t => t.id === id);
if(idx === -1) return;
const t = window.dbData.transferencias[idx];
if(!confirm(`ATENÇÃO: Deseja cancelar e apagar definitivamente o pedido #${id} da Nuvem?\n\nO sistema devolverá automaticamente os equipamentos para o estoque original, caso a remessa ainda não tenha sido dada como "Concluída".`)) return;
let isDevolucao = t.destino === 'Central BH';
let needsRefund = false;
if (isDevolucao) {
if (t.status !== 'Concluído') {
if (t.origem !== 'Admissão/NSI') {
const origemResolvida = window.resolverChaveNucleo ? window.resolverChaveNucleo(t.origem) : t.origem;
const invOrigem = window.obterOuCriarInventarioNucleo ? window.obterOuCriarInventarioNucleo(origemResolvida) : window.dbData.nucleosInventory?.[origemResolvida];
if (invOrigem) {
t.itens.forEach(it => {
if (!invOrigem[it.item]) invOrigem[it.item] = { atual: 0, ideal: 50, baixo: 15 };
invOrigem[it.item].atual += parseInt(it.qtd, 10);
});
needsRefund = true;
}
}
} else {
alert("AVISO: O malote já constava como Recebido em BH. A exclusão vai remover o registro do painel, mas os saldos físicos atuais foram preservados para manter o balanço real.");
}
} else {
const baixaJaAplicada = (t.estoqueBaixado === true) || (typeof t.estoqueBaixado === 'undefined' && ['Pronto para Envio', 'Recibo', 'Em Trânsito', 'Concluído'].includes(String(t.status || '').trim()));
if (t.status !== 'Concluído' && baixaJaAplicada) {
t.itens.forEach(it => {
if (t.origem === 'Central BH') {
window.dbData.inventory[it.item].atual += parseInt(it.qtd, 10);
} else {
const origemResolvida = window.resolverChaveNucleo ? window.resolverChaveNucleo(t.origem) : t.origem;
const invOrigem = window.obterOuCriarInventarioNucleo ? window.obterOuCriarInventarioNucleo(origemResolvida) : window.dbData.nucleosInventory?.[origemResolvida];
if (invOrigem) {
if (!invOrigem[it.item]) invOrigem[it.item] = { atual: 0, ideal: 50, baixo: 15 };
invOrigem[it.item].atual += parseInt(it.qtd, 10);
}
}
});
t.estoqueBaixado = false;
needsRefund = true;
} else if (t.status === 'Concluído') {
alert("AVISO: O malote já consta como Entregue no interior. A exclusão removerá o cartão visual, mas os saldos finais foram preservados.");
}
}
t.canceladoPor = currentUser?.nome || 'GESTOR';
t.canceladoEm = new Date().toLocaleString('pt-BR');
t.arquivado = true;
t.status = 'Cancelado';
if (typeof window.removerIdDoBackupLocal === 'function') {
window.removerIdDoBackupLocal(id);
}
let toSave = ['transferencias'];
if(needsRefund) toSave.push('inventory', 'nucleosInventory');
saveToCloud(toSave).then(() => { renderKanban('suprimento'); renderDevolucoes(); if(needsRefund) { renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo(); } alert("Operação desfeita com sucesso na Nuvem!"); });
};
window.arquivarConcluidos = () => {
if(currentUser.role !== 'admin') return;
if(confirm("Deseja Ocultar/Arquivar todos os malotes 'Concluídos' para limpar as telas?\n\n(Eles não serão apagados, continuarão existindo na aba de Relatórios e Históricos para auditorias futuras).")) {
let modificados = 0; window.dbData.transferencias.forEach(t => { if(t.status === 'Concluído' && !t.arquivado) { t.arquivado = true; modificados++; } });
if(modificados > 0) { saveToCloud(['transferencias']).then(() => { renderKanban('suprimento'); renderDevolucoes(); alert(`Excelente! ${modificados} cartões concluídos foram arquivados com sucesso!`); }); } else { alert("Não há pedidos concluídos poluindo a tela no momento."); }
}
};
window.forcarSincronizacaoCSV = async () => {
if(confirm("ATENÇÃO: Você está prestes a sobrescrever 100% dos saldos de prateleira na Nuvem pelos novos valores da planilha Excel.\n\nDeseja prosseguir?")) {
const bI={"TZPR":{nome:"TZPR",atual:766,ideal:350,baixo:210,icon:"box"},"UPR":{nome:"UPR",atual:206,ideal:30,baixo:18,icon:"box"},"CM":{nome:"CM",atual:339,ideal:350,baixo:210,icon:"box"},"TRAVAS":{nome:"TRAVAS",atual:9157,ideal:1000,baixo:600,icon:"box"},"CINTAS":{nome:"CINTAS",atual:5565,ideal:700,baixo:420,icon:"box"},"FONTE":{nome:"FONTE",atual:1688,ideal:608,baixo:365,icon:"box"},"MANUAL_MONIT":{nome:"MANUAL_MONIT",atual:0,ideal:0,baixo:0,icon:"book-open"},"MANUAL_VITIMA":{nome:"MANUAL_VITIMA",atual:0,ideal:0,baixo:0,icon:"book-open"},"MANUAL_APP":{nome:"MANUAL_APP",atual:0,ideal:0,baixo:0,icon:"smartphone"}};
const dM={"Núcleo Juiz de Fora":{tzpr:10,upr:5,fonte:53,cm:29,cintas:35,travas:36},"Núcleo Uberlândia":{tzpr:74,upr:10,fonte:164,cm:20,cintas:172,travas:216},"Núcleo Montes Claros":{tzpr:61,upr:0,fonte:209,cm:14,cintas:196,travas:184},"Núcleo Gov. Valadares":{tzpr:23,upr:10,fonte:55,cm:23,cintas:57,travas:259},"Núcleo Pouso Alegre":{tzpr:37,upr:17,fonte:154,cm:31,cintas:58,travas:235},"Núcleo Uberaba":{tzpr:49,upr:11,fonte:136,cm:26,cintas:145,travas:183},"Núcleo Patos de Minas":{tzpr:55,upr:7,fonte:72,cm:7,cintas:172,travas:89},"Núcleo Curvelo":{tzpr:0,upr:0,fonte:0,cm:0,cintas:0,travas:0},"Núcleo Teófilo Otoni":{tzpr:51,upr:11,fonte:281,cm:14,cintas:105,travas:98},"Núcleo Ipatinga":{tzpr:32,upr:6,fonte:148,cm:0,cintas:165,travas:139},"Núcleo Unaí":{tzpr:14,upr:4,fonte:68,cm:32,cintas:24,travas:87},"Subnúcleo Alfenas":{tzpr:19,upr:5,fonte:67,cm:35,cintas:58,travas:78},"Núcleo Poços de Caldas":{tzpr:64,upr:11,fonte:180,cm:51,cintas:203,travas:232},"Subnúcleo Muriaé":{tzpr:10,upr:5,fonte:53,cm:29,cintas:35,travas:36},"Subnúcleo Ubá":{tzpr:15,upr:2,fonte:47,cm:7,cintas:29,travas:49},"Subnúcleo Divinópolis":{tzpr:53,upr:10,fonte:147,cm:0,cintas:264,travas:240},"Subnúcleo Guaranésia":{tzpr:26,upr:8,fonte:99,cm:52,cintas:28,travas:7},"Subnúcleo Araxá":{tzpr:5,upr:5,fonte:16,cm:1,cintas:9,travas:29},"Subnúcleo de São João Del Rey":{tzpr:24,upr:4,fonte:28,cm:29,cintas:54,travas:66},"Subnúcleo de Almenara":{tzpr:0,upr:0,fonte:0,cm:0,cintas:0,travas:0},"Subnúcleo Varginha":{tzpr:21,upr:9,fonte:134,cm:47,cintas:154,travas:373},"Subnúcleo Taiobeiras":{tzpr:20,upr:5,fonte:60,cm:15,cintas:40,travas:58},"Subnúcleo Diamantina":{tzpr:0,upr:0,fonte:0,cm:0,cintas:0,travas:0},"Extensão Três Corações":{tzpr:0,upr:0,fonte:0,cm:0,cintas:0,travas:0},"Extensão Gameleira":{tzpr:54,upr:0,fonte:0,cm:0,cintas:0,travas:300}};
let bN={}; nucleosNames.forEach(n=>{let v=dM[n]||{tzpr:0,upr:0,fonte:0,cm:0,cintas:0,travas:0};bN[n]={"TZPR":{atual:v.tzpr,ideal:50,baixo:15},"UPR":{atual:v.upr,ideal:10,baixo:5},"CM":{atual:v.cm||0,ideal:50,baixo:15},"TRAVAS":{atual:v.travas||0,ideal:150,baixo:50},"CINTAS":{atual:v.cintas||0,ideal:150,baixo:50},"FONTE":{atual:v.fonte,ideal:50,baixo:15},"MANUAL_MONIT":{atual:0,ideal:0,baixo:0},"MANUAL_VITIMA":{atual:0,ideal:0,baixo:0},"MANUAL_APP":{atual:0,ideal:0,baixo:0}};});
window.dbData.inventory = bI; window.dbData.nucleosInventory = bN;
saveToCloud(['inventory', 'nucleosInventory']).then(() => { alert("Estoque Nacional atualizado para a Carga Nova com sucesso!"); renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo(); verificarEstoqueCriticoGlobal(); window.renderGraficosAnalytics(); });
}
};
const __pdfImageCache = new Map();
const __pdfImageMissCache = new Map();
const PDF_MISS_TTL_MS = 120000;
const carregarImagem = (url) => {
return new Promise((resolve) => {
if (!url) return resolve(null);
const cacheHit = __pdfImageCache.get(url);
if (cacheHit) return resolve(cacheHit);
const missTs = __pdfImageMissCache.get(url);
if (missTs && (Date.now() - missTs) < PDF_MISS_TTL_MS) {
return resolve(null);
}
const tentarViaFetch = async () => {
try {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 2500);
const resp = await fetch(url, { cache: 'no-store', signal: ctrl.signal });
clearTimeout(timer);
if (!resp.ok) {
__pdfImageMissCache.set(url, Date.now());
return resolve(null);
}
const blob = await resp.blob();
const reader = new FileReader();
reader.onloadend = () => {
const data = typeof reader.result === 'string' ? reader.result : null;
if (data) {
__pdfImageCache.set(url, data);
__pdfImageMissCache.delete(url);
} else {
__pdfImageMissCache.set(url, Date.now());
}
resolve(data);
};
reader.onerror = () => {
__pdfImageMissCache.set(url, Date.now());
resolve(null);
};
reader.readAsDataURL(blob);
} catch (_) {
__pdfImageMissCache.set(url, Date.now());
resolve(null);
}
};
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
try {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const data = canvas.toDataURL('image/png');
if (data) {
__pdfImageCache.set(url, data);
__pdfImageMissCache.delete(url);
}
resolve(data);
} catch (_) {
// Em alguns cenários (CORS/canvas tainted), tenta via fetch/blob.
tentarViaFetch();
}
};
img.onerror = () => tentarViaFetch();
img.src = url;
});
};
const expandirCaminhosImagem = (nome) => {
const limpo = String(nome || '').trim();
if (!limpo) return [];
const safe = encodeURI(limpo);
const out = [
safe,
`./${safe}`,
`/${safe}`,
`assets/${safe}`,
`./assets/${safe}`,
`/assets/${safe}`,
`img/${safe}`,
`./img/${safe}`,
`/img/${safe}`,
`firebase/${safe}`,
`./firebase/${safe}`,
`/firebase/${safe}`
];
if (window?.location?.origin) {
out.push(`${window.location.origin}/${safe}`);
out.push(`${window.location.origin}/assets/${safe}`);
out.push(`${window.location.origin}/img/${safe}`);
out.push(`${window.location.origin}/firebase/${safe}`);
}
return [...new Set(out)];
};
const carregarPrimeiraImagem = async (candidatos = []) => {
const key = `LIST::${candidatos.join('|')}`;
if (__pdfImageCache.has(key)) return __pdfImageCache.get(key);
const missTs = __pdfImageMissCache.get(key);
if (missTs && (Date.now() - missTs) < PDF_MISS_TTL_MS) return null;
for (const nome of candidatos) {
const caminhos = expandirCaminhosImagem(nome);
for (const caminho of caminhos) {
const img = await carregarImagem(caminho);
if (img) {
__pdfImageCache.set(key, img);
__pdfImageMissCache.delete(key);
return img;
}
}
}
__pdfImageMissCache.set(key, Date.now());
return null;
};
const carregarPrimeiraPaginaPdfComoImagem = async (candidatos = []) => {
const key = `PDF::${candidatos.join('|')}`;
if (__pdfImageCache.has(key)) return __pdfImageCache.get(key);
const missTs = __pdfImageMissCache.get(key);
if (missTs && (Date.now() - missTs) < PDF_MISS_TTL_MS) return null;
if (!window.pdfjsLib || typeof window.pdfjsLib.getDocument !== 'function') {
__pdfImageMissCache.set(key, Date.now());
return null;
}
for (const nome of candidatos) {
const caminhos = expandirCaminhosImagem(nome);
for (const caminho of caminhos) {
try {
const loadingTask = window.pdfjsLib.getDocument({ url: caminho });
const pdf = await loadingTask.promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 2 });
const canvas = document.createElement('canvas');
canvas.width = Math.ceil(viewport.width);
canvas.height = Math.ceil(viewport.height);
const ctx = canvas.getContext('2d');
await page.render({ canvasContext: ctx, viewport }).promise;
const data = canvas.toDataURL('image/png');
if (data) {
__pdfImageCache.set(key, data);
__pdfImageMissCache.delete(key);
return data;
}
} catch (_) {
// Tenta o próximo caminho
}
}
}
__pdfImageMissCache.set(key, Date.now());
return null;
};
const gerarQrDataUrl = async (payload) => {
const payloadBase = String(payload ?? '').trim();
const payloadSeguro = payloadBase || 'SIGME|SEM_DADO';
const payloadCurto = payloadSeguro.length > 900 ? payloadSeguro.slice(0, 900) : payloadSeguro;
try {
if (window.QRCode && typeof window.QRCode.toDataURL === 'function') {
return await window.QRCode.toDataURL(payloadCurto, {
width: 120,
margin: 1,
errorCorrectionLevel: 'M'
});
}
} catch (_) {}
try {
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${encodeURIComponent(payloadCurto)}`;
return await carregarImagem(qrUrl);
} catch (_) {
return null;
}
};
function fillTableBlanks(bodyData) { return bodyData.map(row => row.map(cell => (!cell || cell.toString().trim() === "") ? "-" : cell)); }
window.gerarPdfUniversal = async (config) => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF(config.orientacao || 'portrait');
const meses = ["janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"];
// Usar data original do pedido se disponível, senão usar hoje
let dataUsada = new Date();
if (config.dataPedido) {
if (config.dataPedido.includes('-')) {
let parts = config.dataPedido.split('-');
dataUsada = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
} else if (config.dataPedido.includes('/')) {
let parts = config.dataPedido.split('/');
dataUsada = new Date(parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]));
}
}
const dataExt = `${config.cidade || "Belo Horizonte"}, ${dataUsada.getDate().toString().padStart(2, '0')} de ${meses[dataUsada.getMonth()]} de ${dataUsada.getFullYear()}.`;
let y = 15;
// Always add the default header
const imgData = config.tipoLayout === 'USO'
? await carregarImagem('image_e93d6a.png') || await carregarImagem('Imagem cabeçalho.png') || await carregarImagem('Imagem cabeçaho.png') || await carregarImagem('ppmg.png')
: await carregarImagem('Imagem cabeçalho.png') || await carregarImagem('Imagem cabeçaho.png') || await carregarImagem('ppmg.png');
if (imgData) doc.addImage(imgData, 'PNG', 20, y, 22, 28);
doc.setFont("helvetica", "bold"); doc.setFontSize(10); doc.text("GOVERNO DO ESTADO DE MINAS GERAIS", 48, y + 6); doc.text("SECRETARIA DE ESTADO DE JUSTIÇA E SEGURANÇA PÚBLICA", 48, y + 11); doc.text("SUPERINTENDÊNCIA DE GESTÃO DE VAGAS E CUSTÓDIAS", 48, y + 16); doc.text("DIRETORIA DE GESTÃO E MONITORAMENTO ELETRÔNICO", 48, y + 21);
y = 50; // Set y after generic header
const tableStyles = { theme: 'grid', headStyles: { fillColor: [0, 0, 0], textColor: [255, 255, 255], lineColor: [0, 0, 0], lineWidth: 0.4, fontStyle: 'bold' }, styles: { fontSize: 8, cellPadding: 2, valign: 'middle', overflow: 'linebreak', lineColor: [0, 0, 0], lineWidth: 0.4, textColor: [0, 0, 0] }, margin: { left: 15, right: 15 } };
if (config.tipoLayout === 'SOLICITACAO_DME') {
const normalizarItemQtd = (lista = []) => {
const map = {};
(Array.isArray(lista) ? lista : []).forEach(it => {
const itemKey = String(it?.item || '').toUpperCase().trim();
const qtd = parseInt(it?.qtd ?? it?.quantidade ?? 0, 10) || 0;
if (!itemKey || qtd <= 0) return;
if (!map[itemKey]) map[itemKey] = { item: itemKey, qtd: 0 };
map[itemKey].qtd += qtd;
});
return Object.values(map);
};
const resumoItens = (lista = []) => {
const itensNorm = normalizarItemQtd(lista);
if (!itensNorm.length) return 'NENHUM ITEM INFORMADO';
return itensNorm.map(it => `${it.qtd} ${window.formatItemName(it.item)}`).join(' | ');
};
const criarCodigoVerificador = (entrada) => {
const base = String(entrada || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
const recorte = (base.slice(-18) || 'SEIMGDME0000000000').padEnd(18, '0').slice(0, 18);
return `${recorte.slice(0, 6)}-${recorte.slice(6, 12)}-${recorte.slice(12, 18)}`;
};
const criarCrcSimples = (entrada) => {
const s = String(entrada || 'SEI');
let acc = 0;
for (let i = 0; i < s.length; i++) acc = (acc + (s.charCodeAt(i) * (i + 1))) % 0xFFFFFFFF;
return acc.toString(16).toUpperCase().padStart(8, '0');
};
doc.setFont("helvetica", "bold");
doc.setFontSize(14);
doc.text("SOLICITAÇÃO DE ENVIO DE EQUIPAMENTOS", doc.internal.pageSize.width / 2, y, { align: "center" });
y += 15;
const dataSolicitacao = config.dataSolicitacao ? (config.dataSolicitacao.includes('-') ? formatDate(config.dataSolicitacao) : config.dataSolicitacao) : "N/A";
doc.setFontSize(11);
doc.setFont("helvetica", "bold");
doc.text("Unidade Solicitante:", 20, y);
let unitLabelWidth = doc.getTextWidth("Unidade Solicitante:");
doc.setFont("helvetica", "normal");
doc.text(` ${config.unidadeSolicitante ? config.unidadeSolicitante.toUpperCase() : config.destino.toUpperCase()}`, 20 + unitLabelWidth, y);
y += 8;
doc.setFont("helvetica", "bold");
doc.text("Data da Solicitação:", 20, y);
let dataLabelWidth = doc.getTextWidth("Data da Solicitação:");
doc.setFont("helvetica", "normal");
doc.text(` ${dataSolicitacao}`, 20 + dataLabelWidth, y);
y += 8;
doc.setFont("helvetica", "bold");
doc.text("Assunto:", 20, y);
let assuntoLabelWidth = doc.getTextWidth("Assunto:");
doc.setFont("helvetica", "normal");
doc.text(" Solicitação de Dispositivos e Materiais - Modelo 04", 20 + assuntoLabelWidth, y);
y += 15;
const itensSolicitados = normalizarItemQtd(config.itensSolicitados || config.itens || []);
const itensAutorizados = normalizarItemQtd(config.itensAutorizados || config.itens || []);
const resumoSolicitado = resumoItens(itensSolicitados);
const resumoAutorizado = resumoItens(itensAutorizados);
const authInfo = config.autorizacaoDetalhes || ((Array.isArray(config.assinaturasDigitais) && config.assinaturasDigitais.length > 1) ? config.assinaturasDigitais[1] : null);
const houveAutorizacao = !!(authInfo && String(authInfo?.nome || '').trim() && String(authInfo?.nome || '').trim() !== 'AUTORIZADOR NÃO REGISTRADO');
const topoH = 22;
if (y + topoH > doc.internal.pageSize.height - 40) { doc.addPage(); y = 20; }
doc.setDrawColor(75, 85, 99);
doc.setFillColor(248, 250, 252);
doc.roundedRect(20, y - 5, doc.internal.pageSize.width - 40, topoH, 2.2, 2.2, 'FD');
doc.setFont('helvetica', 'bold');
doc.setFontSize(9.4);
doc.setTextColor(17, 24, 39);
doc.text('SOLICITAÇÃO ORIGINAL:', 23, y + 1);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.6);
doc.text(doc.splitTextToSize(resumoSolicitado, doc.internal.pageSize.width - 74), 67, y + 1);
doc.setFont('helvetica', 'bold');
doc.setFontSize(9.4);
doc.text('AUTORIZAÇÃO DA GESTÃO:', 23, y + 9.5);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.6);
const textoAutorizacao = houveAutorizacao ? resumoAutorizado : 'AGUARDANDO ANÁLISE E AUTORIZAÇÃO DA GESTÃO';
doc.text(doc.splitTextToSize(textoAutorizacao, doc.internal.pageSize.width - 80), 74, y + 9.5);
y += topoH + 3;
doc.text("Prezados Diretora,", 20, y);
y += 10;
const introText = `Solicitamos, por meio deste, o envio dos dispositivos e materiais (modelo 04) relacionados abaixo para o ${config.destino}:`;
const introLines = doc.splitTextToSize(introText, doc.internal.pageSize.width - 40);
doc.text(introLines, 20, y);
y += (introLines.length * 5) + 5;
// List requested items
const mp = { 'TZPR': 'Tzprs', 'CM': 'CMs', 'CINTAS': 'Cintas', 'TRAVAS': 'Travas', 'FONTE': 'Fontes Carregadoras', 'UPR': 'UPRs', 'MANUAL_MONIT': 'Manuais do Monitorado', 'MANUAL_VITIMA': 'Manuais da Vítima', 'MANUAL_APP': 'Manuais APP' };
config.itens.forEach(item => {
let itemName = mp[item.item] || window.formatItemName(item.item);
let serialNote = '';
if (['TZPR', 'UPR', 'CM', 'TRAVAS'].includes(item.item)) {
serialNote = ' (favor registrar seriais no envio)';
}
doc.text(`${item.qtd} ${itemName}${serialNote}`, 25, y);
y += 7;
});
y += 10;
const closingText = "Ficamos no aguardo da confirmação do envio e da respectiva listagem com os números de série dos equipamentos para o nosso controle.";
const closingLines = doc.splitTextToSize(closingText, doc.internal.pageSize.width - 40);
doc.text(closingLines, 20, y);
y += (closingLines.length * 5) + 15;
doc.text("Respeitosamente,", 20, y);
y += 12;
// Adiciona a assinatura do Policial Solicitante (dados do sistema)
if (config.assinaturasDigitais && config.assinaturasDigitais.length > 0) {
const pageHeight = doc.internal.pageSize.height;
const sig = config.assinaturasDigitais[0];
const midX = doc.internal.pageSize.width / 2;
const assinaturaHeight = 18;
if (y + assinaturaHeight > pageHeight - 15) {
doc.addPage();
y = 20;
}
doc.setFont("helvetica", "bold");
doc.text(sig.nome.toUpperCase(), midX, y, { align: "center" });
y += 5;
doc.setFont("helvetica", "normal");
doc.text(`Masp: ${sig.masp}`, midX, y, { align: "center" });
y += 5;
doc.text(sig.local || "PPMG-CD/DME", midX, y, { align: "center" });
y += 12;
}
// Carimbo de autorização abaixo da assinatura do solicitante (somente após análise/aprovação real).
if (houveAutorizacao) {
const auth = authInfo || {};
const nomeAuth = String(auth.nome || 'AUTORIZADOR NÃO REGISTRADO').toUpperCase();
const maspAuth = String(auth.masp || '-');
const cargoAuth = String(auth.cargo || 'Policial Penal / Gestão');
const dataAuth = String(auth.dataHora || '-');
const resumoGestao = resumoAutorizado;
const seiBase = `${config.idPedido || '-'}|${nomeAuth}|${maspAuth}|${cargoAuth}|${dataAuth}|${resumoGestao}`;
const codigoVerificador = criarCodigoVerificador(seiBase);
const crc = criarCrcSimples(seiBase);
const textoCabecalhoAutorizado = 'SOLICITAÇÃO AUTORIZADA';
const textoFormalAutorizacao = 'Fica autorizada a retirada dos materiais relacionados abaixo junto ao Estoque Central da Diretoria de Gestão e Monitoramento Eletrônico - DME, situado na Rua Além Paraíba, nº 31, Bairro Lagoinha, Belo Horizonte/MG.';
const linhasTextoFormal = doc.splitTextToSize(textoFormalAutorizacao, doc.internal.pageSize.width - 40);
const linhasItensAutorizados = (itensAutorizados || []).map(it => `- ${it.qtd} ${window.formatItemName(it.item)}`);
const blocoTextoH = 10 + (linhasTextoFormal.length * 4.2) + 4 + (linhasItensAutorizados.length * 4.2) + 6;
if (y + blocoTextoH > doc.internal.pageSize.height - 25) {
doc.addPage();
y = 20;
}
doc.setFont('helvetica', 'bold');
doc.setFontSize(11.2);
doc.setTextColor(15, 23, 42);
doc.text(textoCabecalhoAutorizado, doc.internal.pageSize.width / 2, y, { align: 'center' });
y += 6;
doc.setFont('helvetica', 'normal');
doc.setFontSize(9.2);
doc.setTextColor(0, 0, 0);
doc.text(linhasTextoFormal, 20, y);
y += (linhasTextoFormal.length * 4.2) + 2;
doc.setFont('helvetica', 'bold');
doc.setFontSize(9.2);
doc.text('Quantitativo autorizado pela Gestão:', 20, y);
y += 4.5;
doc.setFont('helvetica', 'normal');
doc.text(linhasItensAutorizados, 24, y);
y += (linhasItensAutorizados.length * 4.2) + 4;
const boxW = doc.internal.pageSize.width - 34;
const boxH = 52;
const boxX = (doc.internal.pageSize.width - boxW) / 2;
if (y + boxH > doc.internal.pageSize.height - 20) {
doc.addPage();
y = 20;
}
// Carimbo em padrão visual SEI/MG para documento oficial.
doc.setDrawColor(30, 41, 59);
doc.setFillColor(255, 255, 255);
doc.roundedRect(boxX, y, boxW, boxH, 1.8, 1.8, 'FD');
doc.setDrawColor(51, 65, 85);
doc.roundedRect(boxX + 1.1, y + 1.1, boxW - 2.2, boxH - 2.2, 1.2, 1.2, 'S');
doc.setFont('helvetica', 'bold');
doc.setFontSize(13.5);
doc.setTextColor(15, 23, 42);
doc.text('PEDIDO APROVADO', doc.internal.pageSize.width / 2, y + 7.8, { align: 'center' });
doc.setFont('helvetica', 'bold');
doc.setFontSize(8.8);
doc.setTextColor(0, 0, 0);
doc.text(`AUTORIZADO POR: ${nomeAuth}`, boxX + 4, y + 14.8);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.3);
doc.text(`MASP: ${maspAuth} | CARGO: ${cargoAuth}`, boxX + 4, y + 19.2);
doc.text(`DATA/HORA DA AUTORIZAÇÃO: ${dataAuth}`, boxX + 4, y + 23.7);
doc.text(`QUANTITATIVO AUTORIZADO PELA GESTÃO:`, boxX + 4, y + 28.1);
const linhasResumoGestao = doc.splitTextToSize(resumoGestao, boxW - 8);
doc.text(linhasResumoGestao.slice(0, 2), boxX + 4, y + 32.3);
doc.setDrawColor(148, 163, 184);
doc.line(boxX + 3, y + 39.5, boxX + boxW - 3, y + 39.5);
doc.setFont('courier', 'normal');
doc.setFontSize(7.2);
doc.text(`Documento assinado eletronicamente nos termos do padrão SEI/MG`, boxX + 4, y + 43.5);
doc.text(`Código verificador: ${codigoVerificador} CRC: ${crc}`, boxX + 4, y + 47.2);
y += boxH + 9;
}
}
// Auto-gerar assinatura digital se estiver vazio (Garantir QR Code em todos os PDFs)
if (!config.assinaturasDigitais || config.assinaturasDigitais.length === 0) {
if (currentUser) {
const hoje = new Date();
config.assinaturasDigitais = [{
nome: currentUser.nome,
masp: currentUser.masp,
cargo: "Policial Penal",
dataHora: hoje.getDate() + " de " + meses[hoje.getMonth()] + " de " + hoje.getFullYear() + " " + hoje.toLocaleTimeString('pt-BR'),
local: currentUser.role === 'nucleo' ? (currentUser.unidades||[])[0] : 'PPMG-CD/DME'
}];
}
}
if (config.tipo === 'tabela_relatorio') {
doc.setFontSize(14); doc.text(config.titulo, doc.internal.pageSize.width/2, y, { align: "center" }); y += 10;
doc.setFont("helvetica", "normal"); doc.setFontSize(9);
const subLines = doc.splitTextToSize(config.subtitulo, doc.internal.pageSize.width - 30);
doc.text(subLines, 15, y);
y += (subLines.length * 4.2) + 2;
let bD = fillTableBlanks(config.body);
let colStyles = {};
if (config.head.length === 5 && config.orientacao === 'landscape') {
const availW = doc.internal.pageSize.width - 20;
colStyles = {
0: { cellWidth: availW * 0.10 },
1: { cellWidth: availW * 0.15 },
2: { cellWidth: availW * 0.15 },
3: { cellWidth: availW * 0.28 },
4: { cellWidth: availW * 0.29 }
};
}
doc.autoTable({ startY: y, head: [config.head], body: bD, ...tableStyles, columnStyles: colStyles, styles: { ...tableStyles.styles, fontSize: config.orientacao === 'landscape' ? 7.5 : 8 } });
y = doc.lastAutoTable.finalY + 10;
if (config.observacoes) {
if (y > (doc.internal.pageSize.height - 40)) { doc.addPage(); y = 20; }
doc.setFont("helvetica", "bold"); doc.setFontSize(10); doc.text("OBSERVAÇÕES:", 15, y);
y += 5; doc.setFont("helvetica", "normal"); doc.setFontSize(9);
const obsLines = doc.splitTextToSize(config.observacoes, doc.internal.pageSize.width - 30);
const boxH = Math.max(15, (obsLines.length * 5) + 5);
doc.rect(15, y, doc.internal.pageSize.width - 30, boxH);
doc.text(obsLines, 17, y + 5);
y += boxH + 10;
}
}
if (config.tipoLayout === 'ENVIO') {
doc.setFont("helvetica", "normal"); doc.setFontSize(11); doc.text("ENTREGA DE EQUIPAMENTOS", 20, y); doc.text(dataExt, 190, y, { align: "right" }); y += 15; // This `y` is 50, which is fine for this layout
doc.setFont("helvetica", "bold"); doc.setFontSize(14); doc.text("LISTAGEM DE ENVIO DISPOSITIVOS", 105, y, { align: "center" }); y += 10;
doc.setFont("helvetica", "normal"); doc.setFontSize(11);
const destLabel = (config.destino === 'Central BH') ? 'DME CENTRAL BH' : (config.destino || "").toUpperCase();
doc.text(`Estabelecimento: ${destLabel}`, 20, y); y += 8;
const tL = doc.splitTextToSize(`Dispositivos e materiais modelos 04: ` + config.textoLista + `.`, 170); doc.text(tL, 20, y); y += (tL.length * 6) + 4;
const codigoRastreio = String(config.codigoRastreio || '').trim();
doc.text(`Número de Volumes: ${config.volumes}`, 20, y);
if (codigoRastreio) doc.text(`Código de Rastreio: #${codigoRastreio}`, 190, y, { align: "right" });
y += 10;
} else if (config.tipoLayout === 'TRIAGEM') {
doc.setFont("helvetica", "normal"); doc.setFontSize(11); doc.text("ENTREGA DE EQUIPAMENTOS\nDISPOSITIVOS E MATERIAIS", 20, y); y += 12;
doc.text(dataExt, 20, y); y += 10;
doc.setFont("helvetica", "bold"); doc.setFontSize(12);
if (config.isDevolucao) {
doc.text(`RECIBO: ${config.tipoRecibo}`, 20, y);
} else {
doc.text(`RECIBO: ${config.numeroRecibo}`, 20, y);
if (config.tipoRecibo) { doc.text(`TIPO: ${config.tipoRecibo}`, 190, y, { align: "right" }); }
}
y += 10;
const repName = config.nomeRepassador || "___________________________";
const repMasp = config.maspRepassador || "_________";
let introText = "";
if (config.isDevolucao) {
introText = `Eu, ${config.remetenteNome || "___________________________"}, Masp.: ${config.remetenteMasp || "_________"}, policial penal, repasso ao Estoque central BH o seguinte quantitativo de dispositivo(s) e material(is) do(a) ${(config.origem || "").toUpperCase()}, que deverão ser encaminhados para Inspeção na SPACECOM.`;
} else {
introText = `Eu, ${repName}, Masp.: ${repMasp}, policial penal, repasso o seguinte quantitativo de dispositivo e material para o setor Spacecom, provenientes da ${(config.origem || "").toUpperCase()} :`;
}
doc.setFont("helvetica", "normal"); doc.setFontSize(11);
const tL = doc.splitTextToSize(introText, 170); doc.text(tL, 20, y); y += (tL.length * 6) + 5;
} else if (config.tipoLayout === 'USO') {
doc.setFont("helvetica", "bold"); doc.setFontSize(14); doc.text("RECIBO DE ENTREGA", 105, y, { align: "center" }); y += 7; doc.text("DISPOSITIVOS E MATERIAIS", 105, y, { align: "center" }); y += 15;
doc.setFont("helvetica", "normal"); doc.setFontSize(11); const t1 = doc.splitTextToSize(`Eu, ${config.nomeRepassador || ""} , policial penal, repasso os seguintes quantitativos de dispositivos e materiais para instalações, na ${config.destino || ""}, conforme descrições abaixo:`, 170); doc.text(t1, 20, y); y += (t1.length * 6) + 4;
const t2 = doc.splitTextToSize(`Dispositivos e materiais modelos 04: ` + config.textoLista + `.`, 170); doc.text(t2, 20, y); y += (t2.length * 6) + 5;
}
if (config.itens && config.itens.length > 0) {
config.itens.forEach(it => {
const keyItem = String(it.item || '');
const seriaisMap = config.seriaisObj || {};
const sData = it.seriais
|| seriaisMap[keyItem]
|| seriaisMap[keyItem.toUpperCase()]
|| seriaisMap[keyItem.toLowerCase()]
|| "";
if(sData && (Array.isArray(sData) ? sData.length > 0 : sData.trim() !== '')) {
let sL = Array.isArray(sData) ? sData : expandirSeriais(sData);
if (!Array.isArray(sData)) {
const bruto = String(sData || '').trim();
const semExpansao = Array.isArray(sL) && sL.length === 1 && sL[0] === 'Nenhum serial informado / Range omitido';
if (bruto && semExpansao) {
sL = bruto
.split(/\r?\n|,|;/)
.map(x => String(x || '').trim())
.filter(Boolean);
}
if (bruto && (!Array.isArray(sL) || sL.length === 0)) {
sL = [bruto];
}
}
let cols = it.cols || (it.item === 'TRAVAS' ? 10 : 5);
let fs = it.fs || (it.item === 'TRAVAS' ? 6.5 : 8);
let pc = it.pc || (it.item === 'TRAVAS' ? 1 : 1.5);
const chunkSize = (it.item === 'TRAVAS') ? 100 : sL.length;
for (let i = 0; i < sL.length; i += chunkSize) {
let sLChunk = sL.slice(i, i + chunkSize);
let bD = [];
for (let idx = 0; idx < sLChunk.length; idx += cols) {
let row = sLChunk.slice(idx, idx + cols);
while (row.length < cols) row.push("-");
bD.push(row.map(c => (!c || c.toString().trim() === "") ? "-" : c));
}
const nomeApresentacao = it.item === 'CM' ? 'CMs' : (it.item === 'TZPR' ? 'TZPRs' : (it.item === 'UPR' ? 'UPRs' : it.item));
const tableHeader = (i === 0) ? `${nomeApresentacao}: ${it.qtd}` : `${nomeApresentacao} (Continuação):`;
doc.autoTable({ startY: y, head: [[ { content: tableHeader, colSpan: cols, styles: { halign: 'left', fontSize: 9, fillColor: [0, 0, 0], textColor: [255, 255, 255], fontStyle: 'bold' } } ]], body: bD, margin: { left: 20, right: 20 }, ...tableStyles, styles: { ...tableStyles.styles, fontSize: fs, cellPadding: pc, halign: 'center' } });
y = doc.lastAutoTable.finalY + 5;
if (y > 260 && (i + chunkSize < sL.length)) { doc.addPage(); y = 20; }
}
}
});
}
if (config.observacoes) {
if (y > 210) { doc.addPage(); y = 20; }
doc.setFont("helvetica", "bold"); doc.setFontSize(10); doc.text("OBSERVAÇÕES:", 20, y);
y += 5; doc.setFont("helvetica", "normal"); doc.setFontSize(9);
const obsLines = doc.splitTextToSize(config.observacoes, 170);
const boxH = Math.max(15, (obsLines.length * 5) + 5);
doc.rect(20, y, 170, boxH);
doc.text(obsLines, 22, y + 5);
y += boxH + 10;
}
// Removido o salto de página fixo (y > 170) que causava a folha em branco desnecessária.
// A lógica de assinaturas no final do código agora decide dinamicamente se precisa de nova página.
doc.setFont("helvetica", "normal"); doc.setFontSize(10); doc.setTextColor(0, 0, 0);
const pageHeight = doc.internal.pageSize.height;
if (config.tipoLayout === 'ENVIO') {
// Organiza as variáveis prevenindo nulos
let transData = config.transportador && config.transportador.dataHora ? config.transportador.dataHora.split(' ')[0] : "___/___/_____";
let transNome = config.transportador ? config.transportador.nome.toUpperCase() : "__________________________________";
let transMasp = config.transportador ? config.transportador.masp : "__________________________________";
let aceitData = config.aceite && config.aceite.data ? config.aceite.data.split(' ')[0] : "___/___/_____";
let aceitNome = config.aceite ? config.aceite.nome.toUpperCase() : "__________________________________";
let aceitMasp = config.aceite ? config.aceite.masp : "__________________________________";
// Títulos das colunas
doc.setFont("helvetica", "bold");
doc.text("Responsável pelo Transporte (Escolta/VTR):", 20, y);
doc.text("Responsável pelo Recebimento (Polo):", 115, y);
y += 10;
doc.setFont("helvetica", "normal");
// Linha 1: Data
doc.text(`Data: ${transData}`, 20, y);
doc.text(`Data: ${aceitData}`, 115, y);
y += 10;
// Linha 2: Nome
if (config.transportador) {
doc.setFont("helvetica", "bold"); doc.text(`${transNome}`, 20, y); doc.setFont("helvetica", "normal");
} else {
doc.text(`Nome: ${transNome}`, 20, y);
}
if (config.aceite) {
doc.setFont("helvetica", "bold"); doc.text(`${aceitNome}`, 115, y); doc.setFont("helvetica", "normal");
} else {
doc.text(`Nome: ${aceitNome}`, 115, y);
}
y += 10;
// Linha 3: Assinatura
doc.text("Assinatura: ______________________________", 20, y);
doc.text("Assinatura: ______________________________", 115, y);
y += 10;
// Linha 4: MASP/RG
if (y + 20 > pageHeight - 15) { doc.addPage(); y = 20; }
doc.text(config.transportador ? `RG/Masp: ${transMasp}` : `RG/Masp: ${transMasp}`, 20, y);
doc.text(config.aceite ? `RG/Masp: ${aceitMasp}` : `RG/Masp: ${aceitMasp}`, 115, y);
} else if (config.tipoLayout === 'TRIAGEM') {
doc.text("Por ser verdade, assino o presente recibo:", 20, y); y += 8; doc.text(dataExt, 20, y); y += 20;
if (config.isDevolucao) {
if (y + 58 > pageHeight - 15) { doc.addPage(); y = 20; }
} else {
if (y + 30 > pageHeight - 15) { doc.addPage(); y = 20; }
}
if (config.isDevolucao) {
doc.text("______________________________________________________", 20, y); y += 5;
doc.setFont("helvetica", "bold"); doc.text(config.remetenteNome ? config.remetenteNome.toUpperCase() : "______________________________________________________", 20, y); y += 5;
doc.setFont("helvetica", "normal"); doc.text(`Masp: ${config.remetenteMasp || ""}`, 20, y); y += 5;
doc.text("Policial Penal", 20, y); y += 5;
doc.text((config.origem || "").toUpperCase(), 20, y); y += 15;
doc.text("______________________________________________________", 20, y); y += 5;
doc.setFont("helvetica", "bold"); doc.text(config.nomeRepassador ? config.nomeRepassador.toUpperCase() : "______________________________________________________", 20, y); y += 5;
doc.setFont("helvetica", "normal"); doc.text(`Masp: ${config.maspRepassador || ""}`, 20, y); y += 5;
doc.text("Policial Penal", 20, y); y += 5;
doc.text("PPMG-CD/DME", 20, y);
} else {
doc.text("___________________________________", 20, y); doc.text("___________________________________", 115, y); y += 5;
doc.text("Funcionário SPACECOM", 20, y); doc.text(config.nomeRepassador || "", 115, y); y += 5;
doc.text(`Masp: ${config.maspRepassador || ""}`, 115, y); y += 5; doc.text("Policial Penal", 115, y); y += 5; doc.text(config.lotacaoRepassador || "", 115, y);
}
} else if (config.tipoLayout === 'USO') {
doc.text(dataExt, 20, y); y += 15;
if (y + 30 > pageHeight - 15) { doc.addPage(); y = 20; }
doc.text("______________________________________________________", 20, y); y += 5;
doc.setFont("helvetica", "bold"); doc.text((config.nomeRecebedor || "").toUpperCase(), 20, y); y += 5;
doc.setFont("helvetica", "normal"); doc.text(`Masp: ${config.maspRecebedor || ""}`, 20, y); y += 5;
doc.text("Policial Penal", 20, y); y += 5;
doc.text((config.destino || "").toUpperCase(), 20, y); y += 15;
doc.text("______________________________________________________", 20, y); y += 5;
doc.setFont("helvetica", "bold"); doc.text((config.nomeRepassador || "").toUpperCase(), 20, y); y += 5;
doc.setFont("helvetica", "normal"); doc.text(`Masp: ${config.maspRepassador || ""}`, 20, y); y += 5;
doc.text("Policial Penal", 20, y); y += 5;
doc.text((config.lotacaoRepassador || "").toUpperCase(), 20, y);
}
// RENDERIZAR ASSINATURAS DIGITAIS (ESTILO SEI) NO RODAPÉ
if (config.assinaturasDigitais && config.assinaturasDigitais.length > 0) {
const pageHeight = doc.internal.pageSize.height;
const linhaAssinaturaHeight = 4;
const qrSize = 20;
const textoLineHeight = 3.4;
const textoBaseLines = 3;
const signatureBlockHeight = linhaAssinaturaHeight + qrSize + 4 + (textoBaseLines * textoLineHeight) + 2;
const totalSignaturesHeight = config.assinaturasDigitais.length * signatureBlockHeight + (config.isDevolucao && config.assinaturasDigitais.length === 1 ? 10 : 0);
const marginBottom = config.isDevolucao ? 40 : 15; // Margem aumentada para devoluções (QR + texto SEI + campo recebimento)
const desiredSignatureStartY = pageHeight - totalSignaturesHeight - marginBottom;
// Se o conteúdo atual já invadiu a área reservada para o rodapé de assinaturas, pula página
if (y > desiredSignatureStartY - 5) {
doc.addPage();
y = desiredSignatureStartY;
} else {
y = Math.max(y, desiredSignatureStartY);
}
for (const sig of config.assinaturasDigitais) {
doc.setDrawColor(200); doc.setLineWidth(0.1);
doc.line(20, y, 190, y); y += 4;
let sDH = String(sig.dataHora || "").replace(',', '');
if (sDH.includes('/') && !sDH.includes(' de ')) {
const p = sDH.split(' '); const d = p[0].split('/');
if (d.length === 3) { sDH = parseInt(d[0]) + " de " + meses[parseInt(d[1]) - 1] + " de " + d[2] + (p[1] ? " " + p[1] : ""); }
}
const qrText = `Assinante ${sig.nome} Masp ${sig.masp} Cargo/Função ${sig.cargo || 'Policial Penal'} Data/Hora ${sDH} Tipo Login/Senha`;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrText)}`;
try { const qrImg = await carregarImagem(qrUrl); if (qrImg) doc.addImage(qrImg, 'PNG', 20, y, 20, 20); } catch(e) {}
doc.setFont("helvetica", "normal"); doc.setFontSize(7.5); doc.setTextColor(80);
const dPart = sDH.includes(' de ') ? [sDH.substring(0, sDH.lastIndexOf(' ')), sDH.substring(sDH.lastIndexOf(' ') + 1)] : (sDH.includes(' ') ? sDH.split(' ') : [sDH, '00:00:00']);
const timeStr = (dPart[1] || '00:00').substring(0, 5);
const textoSEI = `Documento assinado eletronicamente por ${sig.nome}, ${sig.cargo || 'Policial Penal'}, em ${dPart[0]}, às ${timeStr}, conforme horário oficial de Brasília, com fundamento no art. 6º, § 1º, do Decreto nº 47.222, de 26 de julho de 2017.`;
const lines = doc.splitTextToSize(textoSEI, 155);
doc.text(lines, 43, y + 2);
y += Math.max(24, 6 + (lines.length * 3.4));
}
}
doc.save(config.nomeArquivo || "Recibo_SIGME.pdf");
};
window.renderKanban = function(tipo) {
if(tipo !== 'suprimento') { renderDevolucoes(); return; }
const cP = $('col-pendentes'), cPr = $('col-pronto'), cR = $('col-recibo'), cC = $('col-concluido');
if(!cP || !cPr || !cR || !cC) return; cP.innerHTML = ''; cPr.innerHTML = ''; cR.innerHTML = ''; cC.innerHTML = '';
const unidadesCanonicas = (currentUser.role === 'nucleo')
? (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : (currentUser.unidades||[])).map(u => window.obterChaveCanonicaUnidade(u)).filter(Boolean)
: [];
let list = (window.obterListaTransferenciasMescladaHistoricoLocal ? window.obterListaTransferenciasMescladaHistoricoLocal() : (window.dbData.transferencias || [])).filter(tr => {
if (String(tr?.status || '').trim() === 'Cancelado') return false;
if(currentUser.role === 'nucleo') {
const isOrigemDoUsuario = window.unidadePertenceAoUsuario(tr.origem, unidadesCanonicas);
const isDestinoDoUsuario = window.unidadePertenceAoUsuario(tr.destino, unidadesCanonicas);
if (!isOrigemDoUsuario && !isDestinoDoUsuario) return false;
}
return tr.destino !== 'Central BH' && !tr.arquivado;
}).reverse();
list.forEach(tr => {
const canAuth = currentUser.role === 'admin' || currentUser.role === 'central';
const isMyN = currentUser.role === 'nucleo' && window.unidadePertenceAoUsuario(tr.destino, unidadesCanonicas);
const isMyO = (currentUser.role === 'nucleo' && window.unidadePertenceAoUsuario(tr.origem, unidadesCanonicas)) || (tr.origem === 'Central BH' && canAuth);
let actions = []; let mainAction = null;
if (tr.status === 'Pendente' && canAuth) mainAction = { label: 'Analisar & Autorizar', icon: 'search', onclick: `abrirModalAutorizar('${tr.id}')`, color: 'btn-orange' };
else if (tr.status === 'Autorizado' && isMyO) mainAction = { label: 'Iniciar Preparação', icon: 'package', onclick: `workflowAction('${tr.id}', 'Em Preparação')`, color: 'btn-blue' };
else if (tr.status === 'Em Preparação' && isMyO) mainAction = { label: 'Inserir Seriais', icon: 'barcode', onclick: `abrirModalPreparar('${tr.id}')`, color: 'btn-gold' };
else if (tr.status === 'Pronto para Envio' && isMyO) { mainAction = { label: 'Despachar Malote', icon: 'truck', onclick: `abrirModalRetirada('${tr.id}')`, color: 'btn-blue' }; actions.push({ label: 'Editar Malote', icon: 'edit-3', onclick: `abrirModalPreparar('${tr.id}')` }); }
else if (tr.status === 'Recibo' && (isMyN || currentUser.role === 'admin')) mainAction = { label: 'Confirmar Chegada', icon: 'check-circle-2', onclick: `workflowAction('${tr.id}', 'Concluído')`, color: 'btn-green' };
if(['Pendente', 'Autorizado', 'Em Preparação', 'Pronto para Envio', 'Em Trânsito', 'Recibo', 'Concluído'].includes(tr.status)) {
actions.push({ label: 'PDF Solicitação', icon: 'file-text', onclick: `gerarPDFSolicitacaoSalva('${tr.id}')` });
if (tr.status !== 'Pendente') {
actions.push({ label: 'Guia de Transporte', icon: 'truck', onclick: `gerarPDFEnvioSalvo('${tr.id}')` });
}
}
actions.push({ label: 'Etiqueta de Caixa', icon: 'tag', onclick: `window.gerarEtiquetaMalote('${tr.id}')` });
if (currentUser.role === 'admin' || (currentUser.role === 'central' && tr.status !== 'Concluído')) { actions.push({ label: 'Cancelar / Excluir', icon: 'trash-2', onclick: `apagarTransferencia('${tr.id}')`, class: 'text-red-500 hover:!bg-red-500/10' }); }
let badgeClass = "text-gray-400 border-gray-600/50";
if(tr.status === 'Autorizado') badgeClass = "text-orange-500 border-orange-500/50";
else if(tr.status === 'Em Preparação') badgeClass = "text-yellow-400 border-yellow-500/50";
else if(tr.status === 'Pronto para Envio') badgeClass = "text-gold-500 border-gold-600/50";
else if(tr.status === 'Em Trânsito') badgeClass = "text-blue-400 border-blue-500/50";
else if(tr.status === 'Recibo') badgeClass = "text-purple-400 border-purple-500/50";
else if(tr.status === 'Concluído') badgeClass = "text-green-500 border-green-500/50";
// Ponto 2: Texto dinâmico para Pronto para Envio
let displayStatus = tr.status;
if(tr.status === 'Pronto para Envio' && (currentUser.role === 'nucleo' || window.unidadePertenceAoUsuario(tr.destino, unidadesCanonicas))) {
displayStatus = 'Pronto para Retirada';
badgeClass = "text-green-400 border-green-500/50 bg-green-900/20";
}
let tagsHtml = '';
if(tr.itens) {
tagsHtml = `${tr.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item).toLowerCase()} `).join('')}
`;
}
let seriaisHtml = '';
if (tr.seriais && typeof tr.seriais === 'object') {
const blocosSeriais = (tr.itens || []).map(it => {
const itemKey = String(it.item || '').toUpperCase();
const bruto = String(tr.seriais[itemKey] || '').trim();
if (!bruto) return '';
const lista = (window.expandirSeriais ? window.expandirSeriais(bruto) : bruto.split(/\r?\n|,/))
.filter(s => s && s !== 'Nenhum serial informado / Range omitido');
if (!lista.length) return '';
const preview = lista.slice(0, 4).join(', ');
const extra = lista.length > 4 ? ` +${lista.length - 4}` : '';
const titulo = bruto.replace(/"/g, '"');
return `${window.formatItemName(itemKey)}
${preview}${extra}
`;
}).filter(Boolean);
if (blocosSeriais.length) {
seriaisHtml = `Seriais informados
${blocosSeriais.join('')}
`;
}
}
const obsSolicitante = String(tr.observacoesSolicitante || '').trim();
const obsHtml = obsSolicitante
? `Observacoes do Solicitante
${obsSolicitante.replace(//g, '>')}
`
: '';
const dropdownHtml = actions.length > 0 ? `
${actions.map(a => ` ${a.label} `).join('')}
` : '';
let transInfo = tr.transportador ? ` ${tr.transportador.nome} - VTR: ${tr.transportador.lotacao}
` : '';
let aceiteInfo = tr.aceitePor ? `Recebido por: ${tr.aceitePor} ${tr.aceiteData}
` : '';
let mainBtnHtml = mainAction ? ` ${mainAction.label} ` : '';
const card = `
#${tr.id}
${displayStatus}
${dropdownHtml}
Destino: ${tr.destino}
Origem: ${tr.origem}
${tagsHtml}
${seriaisHtml}
${obsHtml}
${transInfo}${aceiteInfo}
${mainBtnHtml}
`;
if(tr.status === 'Pendente') { cP.innerHTML += card; } else if(tr.status === 'Autorizado' || tr.status === 'Em Preparação' || tr.status === 'Pronto para Envio') { cPr.innerHTML += card; } else if(tr.status === 'Em Trânsito' || tr.status === 'Recibo') { cR.innerHTML += card; } else if (tr.status === 'Concluído') { cC.innerHTML += card; }
});
['pendentes','pronto','recibo','concluido'].forEach(id => { const col = $(`col-${id}`); const el = $(`count-${id}`); if(el && col) { el.innerText = col.children.length; if(col.children.length === 0) col.innerHTML = `Fila Vazia
`; } }); lucide.createIcons();
}
window.DIRETORA_NOME_COMPLETO = 'Juliana Aparecida de Camargos Silva Ferreira';
window.DIRETORA_CARGO = 'Subdiretora de Gestão e Monitoramento Eletrônico';
window.ehDiretoraJuliana = function(nome) {
const texto = String(nome || '').toUpperCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
if (!texto) return false;
return texto.includes('JULIANA') && (texto.includes('CAMARGOS') || texto.includes('SILVA FERREIRA'));
};
window.normalizarAssinaturaAutorizador = function(dados = {}) {
const nomeBase = String(dados.nome || '').trim();
const isDiretora = window.ehDiretoraJuliana(nomeBase);
return {
nome: isDiretora ? window.DIRETORA_NOME_COMPLETO : (nomeBase || 'AUTORIZADOR NÃO REGISTRADO'),
masp: String(dados.masp || '-'),
cargo: isDiretora ? window.DIRETORA_CARGO : String(dados.cargo || 'Policial Penal / Gestão'),
dataHora: String(dados.dataHora || new Date().toLocaleString('pt-BR')),
local: String(dados.local || 'PPMG-CD/DME')
};
};
window.obterAssinaturasSolicitacao = function(tr, solicitante) {
const arr = [{
nome: solicitante.nome,
masp: solicitante.masp,
cargo: 'Policial Penal Solicitante',
dataHora: solicitante.dataHora,
local: solicitante.local
}];
const nomeAnalise = String(tr?.analisadoPor || tr?.autorizadoPor || '').trim();
const maspAnalise = String(tr?.analisadoMasp || tr?.autorizadoMasp || '').trim();
const dataAnalise = String(tr?.dataHoraAnalise || '').trim();
const houveAnalise = !!(nomeAnalise && maspAnalise && dataAnalise);
if (!houveAnalise) return arr;
const auth = window.normalizarAssinaturaAutorizador({
nome: tr?.analisadoPor || tr?.autorizadoPor || '',
masp: tr?.analisadoMasp || tr?.autorizadoMasp || '-',
cargo: tr?.analisadoCargo || tr?.autorizadoCargo || 'Policial Penal / Gestão',
dataHora: tr?.dataHoraAnalise || tr?.dataHoraRegistro || new Date().toLocaleString('pt-BR'),
local: 'PPMG-CD/DME'
});
arr.push(auth);
return arr;
};
window.gerarPDFSolicitacaoSalva = async (id) => {
return window.gerarPDFEnvioSalvo(id, true);
};
window.gerarPDFEnvioSalvo = async (id, forceSolicitacao = false) => {
const tr = (window.obterListaTransferenciasMescladaHistoricoLocal ? window.obterListaTransferenciasMescladaHistoricoLocal() : (window.dbData.transferencias || [])).find(x => x.id === id); if (!tr) return;
const isDevolucao = tr.destino === "Central BH";
const cid = tr.origem !== "Central BH" ? tr.origem.replace("Núcleo ", "").replace("Subnúcleo ", "") : "Belo Horizonte";
const mp = { 'TZPR': 'Tzprs', 'CM': 'CMs', 'CINTAS': 'Cintas', 'TRAVAS': 'Travas', 'FONTE': 'Fontes Carregadoras', 'UPR': 'UPRs', 'MANUAL_MONIT': 'Manuais do Monitorado', 'MANUAL_VITIMA': 'Manuais da Vítima', 'MANUAL_APP': 'Manuais APP' };
let qA = []; (tr.itens || []).forEach(it => { if(it.item !== 'CINTAS' && it.item !== 'FONTE' && !it.item.startsWith('MANUAL')) qA.push(`${it.qtd} ${mp[it.item] || window.formatItemName(it.item)} (seriais abaixo)`); else qA.push(`${it.qtd} ${mp[it.item] || window.formatItemName(it.item)}`); });
let tL = qA.length > 1 ? qA.join(", ").replace(/, ([^,]*)$/, " e $1") : (qA[0] || "");
if (forceSolicitacao || tr.status === 'Pendente') {
const solicitante = {
nome: tr.registradoPor || "N/A",
masp: tr.registradoMasp || "N/A",
local: tr.registradoLocal || tr.destino || "Central BH",
dataHora: tr.dataHoraRegistro || new Date().toLocaleString('pt-BR')
};
const assinaturasSolicitacao = window.obterAssinaturasSolicitacao(tr, solicitante);
await window.gerarPdfUniversal({
tipoLayout: 'SOLICITACAO_DME',
idPedido: tr.id,
destino: tr.destino,
unidadeSolicitante: solicitante.local,
dataSolicitacao: tr.data || tr.dataHoraRegistro,
itens: tr.itens,
itensSolicitados: tr.itensSolicitados || tr.itensOriginalSolicitacao || tr.itens,
itensAutorizados: tr.itensAutorizados || tr.itens,
cidade: "Belo Horizonte", // Assuming Belo Horizonte as fixed city for now
nomeArquivo: `Solicitacao_Envio_${tr.id}.pdf`,
assinaturasDigitais: assinaturasSolicitacao,
autorizacaoDetalhes: assinaturasSolicitacao[1] || null
});
return; // Exit after generating the specific PDF
}
// Melhorar captura do preenchedor com fallbacks para registros antigos
const nomePreenchedor = tr.registradoPor || tr.responsavel || (tr.transportador ? tr.transportador.nome : (tr.entregador ? tr.entregador.nome : ""));
const maspPreenchedor = tr.registradoMasp || tr.masp || (tr.transportador ? tr.transportador.masp : (tr.entregador ? tr.entregador.masp : "N/A"));
// Preparar Assinaturas Digitais (Estilo SEI)
let assinaturasDigitais = [];
if (nomePreenchedor) {
assinaturasDigitais.push({
nome: nomePreenchedor,
masp: maspPreenchedor,
cargo: "Policial Penal",
dataHora: tr.dataHoraRegistro || (tr.data + " 08:00:00"),
local: tr.origem
});
}
// Assinatura de quem recebeu (se concluído)
if (tr.aceitePor && tr.status === 'Concluído') {
assinaturasDigitais.push({
nome: tr.aceitePor,
masp: tr.aceiteMasp || "N/A",
cargo: "Policial Penal",
dataHora: tr.aceiteData,
local: tr.destino
});
}
if (isDevolucao) {
let unitPrefix = "";
let clean = tr.origem.replace(/Núcleo |Subnúcleo |Extensão /gi, '').trim().toUpperCase();
let words = clean.split(' ').filter(w => w.length > 2);
if (words.length === 1) unitPrefix = words[0].substring(0, 3);
else unitPrefix = words.map(w => w[0]).join('').substring(0, 3);
if (tr.origem === 'Admissão/NSI') unitPrefix = "ADM/NSI";
let seqMatch = tr.id.match(/\d+/);
let seq = seqMatch ? seqMatch[0] : "01";
let tipoDev = `DEV${seq}${unitPrefix}`;
const obs = prompt("Deseja inserir alguma observação neste documento? (Deixe em branco se não)");
await window.gerarPdfUniversal({
tipoLayout: 'TRIAGEM', isDevolucao: true, origem: tr.origem, numeroRecibo: tr.id, tipoRecibo: tipoDev,
itens: tr.itens, seriaisObj: tr.seriais || {}, observacoes: obs,
nomeRepassador: tr.aceitePor || "", maspRepassador: tr.aceiteMasp || "",
remetenteNome: nomePreenchedor, remetenteMasp: maspPreenchedor !== "N/A" ? maspPreenchedor : "",
assinaturasDigitais: assinaturasDigitais, dataPedido: tr.data || tr.dataHoraRegistro,
cidade: "Belo Horizonte", nomeArquivo: `Recibo_Devolucao_${tr.id}.pdf`
});
} else {
let transpObj = tr.transportador || tr.entregador || null;
let aceiteObj = tr.aceitePor ? { nome: tr.aceitePor, masp: tr.aceiteMasp || "", data: tr.aceiteData } : null;
const obs = prompt("Deseja inserir alguma observação neste documento? (Deixe em branco se não)");
await window.gerarPdfUniversal({
tipoLayout: 'ENVIO', destino: tr.destino, volumes: (tr.volumes||1).toString().padStart(2, '0'), textoLista: tL, observacoes: obs,
codigoRastreio: tr.id,
itens: tr.itens, seriaisObj: tr.seriais || {}, cidade: cid, nomeArquivo: `Guia_Transporte_${tr.id}.pdf`, transportador: transpObj, aceite: aceiteObj,
dataPedido: tr.data || tr.dataHoraRegistro, assinaturasDigitais: assinaturasDigitais
});
}
};
window.gerarEtiquetaMalote = async (id) => {
const tr = window.dbData.transferencias.find(x => x.id === id); if (!tr) return;
const { jsPDF } = window.jspdf;
const doc = new jsPDF('portrait', 'mm', 'a4');
const imgData = await carregarImagem('Imagem cabeçalho.png') || await carregarImagem('ppmg.png');
const desenharEtiqueta = (yOffset) => {
doc.setDrawColor(0, 0, 0); doc.setLineWidth(1.5); doc.rect(10, yOffset + 10, 190, 128.5);
doc.setLineWidth(0.5); doc.rect(12, yOffset + 12, 186, 124.5);
if (imgData) doc.addImage(imgData, 'PNG', 18, yOffset + 16, 22, 28);
doc.setFont("helvetica", "bold"); doc.setFontSize(11); doc.setTextColor(0, 0, 0);
doc.text("GOVERNO DO ESTADO DE MINAS GERAIS", 45, yOffset + 22);
doc.text("SECRETARIA DE ESTADO DE JUSTIÇA E SEGURANÇA PÚBLICA", 45, yOffset + 27);
doc.text("DIRETORIA DE GESTÃO E MONITORAMENTO ELETRÔNICO", 45, yOffset + 32);
doc.text("SISTEMA SIGME - LOGÍSTICA ESTADUAL", 45, yOffset + 37);
doc.setLineWidth(0.5); doc.line(12, yOffset + 48, 198, yOffset + 48);
doc.setFontSize(14); doc.text(`REMETENTE: ${tr.origem.toUpperCase()}`, 18, yOffset + 58);
doc.text(`DESTINATÁRIO:`, 18, yOffset + 70);
doc.setFont("helvetica", "bold");
let destText = tr.destino.toUpperCase(); let fontSize = 72; doc.setFontSize(fontSize);
while (doc.getTextWidth(destText) > 175 && fontSize > 20) { fontSize -= 2; doc.setFontSize(fontSize); }
doc.text(destText, 105, yOffset + 98, { align: "center" });
doc.setFontSize(16); doc.text(`CÓDIGO DE RASTREIO: #${tr.id}`, 105, yOffset + 118, { align: "center" });
doc.setFontSize(12); doc.text(`DATA DA REMESSA: ${new Date().toLocaleDateString('pt-BR')}`, 105, yOffset + 126, { align: "center" });
};
desenharEtiqueta(0);
doc.setDrawColor(150, 150, 150); doc.setLineWidth(0.5);
doc.circle(6, 146.5, 1); doc.circle(6, 150.5, 1); doc.line(6.5, 147.2, 11, 148.5); doc.line(6.5, 149.8, 11, 148.5); doc.setFillColor(150, 150, 150); doc.circle(8.5, 148.5, 0.4, 'F');
doc.circle(204, 146.5, 1); doc.circle(204, 150.5, 1); doc.line(203.5, 147.2, 199, 148.5); doc.line(203.5, 149.8, 199, 148.5); doc.circle(201.5, 148.5, 0.4, 'F');
doc.setLineDashPattern([3, 3], 0); doc.line(13, 148.5, 197, 148.5); doc.setLineDashPattern([], 0);
doc.setFont("helvetica", "bold"); doc.setFontSize(8); doc.setTextColor(150, 150, 150); doc.text("CORTAR AQUI", 105, 147.5, { align: "center" });
desenharEtiqueta(148.5);
doc.save(`Etiqueta_Caixa_${tr.id}.pdf`);
};
window.editarDevolucaoId = null;
window.editarDevolucao = (id) => {
const t = window.dbData.transferencias.find(x => x.id === id);
if (!t) return;
window.editarDevolucaoId = id;
window.devolucaoEditandoIdx = null;
$('modal-devolucao').classList.replace('hidden', 'flex');
const s = $('dev-nucleo'); const d = $('div-dev-nucleo');
if (currentUser.role !== 'nucleo' || (currentUser.unidades || []).length > 1) {
d.classList.remove('hidden');
s.innerHTML = '';
let unidadesPermitidas = (currentUser.role === 'nucleo' && currentUser.unidades) ? currentUser.unidades : nucleosNames;
unidadesPermitidas.forEach(n => { s.innerHTML += `${n} `; });
s.value = t.origem;
} else {
d.classList.add('hidden');
}
window.devolucaoItensPendentes = [];
t.itens.forEach(it => {
let seriaisText = "";
if (t.seriais && t.seriais[it.item]) {
seriaisText = t.seriais[it.item];
}
window.devolucaoItensPendentes.push({ item: it.item, qtd: it.qtd, seriais: seriaisText, comDefeito: it.comDefeito || false });
});
const btnAdicionar = document.getElementById('btn-dev-adicionar');
if (btnAdicionar) {
btnAdicionar.innerHTML = ' Adicionar à Caixa de Devolução';
if (window.lucide) lucide.createIcons();
}
const btnGerar = document.querySelector('button[onclick="gerarDevolucao()"]');
if (btnGerar) {
btnGerar.innerHTML = ' Salvar Edição do Malote';
if (window.lucide) lucide.createIcons();
}
atualizarListaDevolucao();
};
window.renderDevolucoes = function() {
const colT = $('col-dev-transito'), colC = $('col-dev-concluido'); if(!colT || !colC) return;
colT.innerHTML = ''; colC.innerHTML = '';
let list = (window.obterListaTransferenciasMescladaHistoricoLocal ? window.obterListaTransferenciasMescladaHistoricoLocal() : (window.dbData.transferencias || [])).filter(t => t.destino === 'Central BH' && !t.arquivado).reverse();
const unidadesLower = (currentUser.role === 'nucleo')
? (currentUser.unidades||[]).map(u => (u || '').trim().toLowerCase())
: [];
if(currentUser.role === 'nucleo') {
list = list.filter(t => {
const origemLower = (t.origem || '').trim().toLowerCase();
const destinoLower = (t.destino || '').trim().toLowerCase();
return unidadesLower.includes(origemLower) || unidadesLower.includes(destinoLower);
});
}
list.forEach(t => {
const origemLower = (t.origem || '').trim().toLowerCase();
let actions = []; let mainBtnHtml = '';
if (t.status === 'Em Trânsito' && currentUser.role !== 'nucleo') mainBtnHtml = ` Acusar Chegada `;
if (t.status === 'Em Trânsito' && (currentUser.role === 'admin' || currentUser.role === 'central' || unidadesLower.includes(origemLower))) {
actions.push({ label: 'Editar Malote', icon: 'edit-3', onclick: `editarDevolucao('${t.id}')` });
}
actions.push({ label: 'Gerar PDF Oficial', icon: 'file-text', onclick: `gerarPDFEnvioSalvo('${t.id}')` });
actions.push({ label: 'Etiqueta da Caixa', icon: 'tag', onclick: `window.gerarEtiquetaMalote('${t.id}')` });
if (currentUser.role === 'admin' || (currentUser.role === 'central' && t.status !== 'Concluído')) { actions.push({ label: 'Cancelar / Excluir', icon: 'trash-2', onclick: `apagarTransferencia('${t.id}')`, class: 'text-red-500 hover:!bg-red-500/10' }); }
let tagsHtml = '';
if(t.itens) {
tagsHtml = `${t.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item).toLowerCase()} `).join('')}
`;
}
const dropdownHtml = `
${actions.map(a => ` ${a.label} `).join('')}
`;
let entInfo = t.entregador ? `Transportado por: ${t.entregador.nome} (${t.entregador.lotacao})
` : '';
let recInfo = t.aceitePor ? `Validado por: ${t.aceitePor} - ${t.aceiteData}
` : '';
const card = `
Devolução #${t.id}
${t.status}
${dropdownHtml}
Emissor: ${t.origem}
${tagsHtml}
${entInfo}${recInfo}${mainBtnHtml}
`;
if(t.status === 'Em Trânsito') { colT.innerHTML += card; } else { colC.innerHTML += card; }
});
const countT = $('count-dev-transito'); if(countT) countT.innerText = colT.children.length; if(colT.children.length === 0) colT.innerHTML = `Fila Vazia
`; if(colC.children.length === 0) colC.innerHTML = `Fila Vazia
`; lucide.createIcons();
}
window.workflowAction = function(id, nextStatus) {
const t = window.dbData.transferencias.find(x => x.id === id); if(!t) return;
if (nextStatus === 'Concluído') { t.itens.forEach(it => { const inventarioDestino = window.obterOuCriarInventarioNucleo(t.destino); if(!inventarioDestino[it.item]) inventarioDestino[it.item] = {atual:0, ideal:50, baixo:15}; inventarioDestino[it.item].atual += parseInt(it.qtd, 10); }); t.aceitePor = currentUser.nome; t.aceiteMasp = currentUser.masp; t.aceiteData = new Date().toLocaleString('pt-BR'); }
t.status = nextStatus; saveToCloud(['transferencias', 'inventory', 'nucleosInventory']); renderKanban('suprimento');
};
window.obterUnidadesUsuario = () => {
const unidadesRaw = currentUser?.unidades;
let unidades = [];
if (Array.isArray(unidadesRaw)) {
unidades = unidadesRaw;
} else if (typeof unidadesRaw === 'string') {
unidades = unidadesRaw.split(',').map(u => u.trim()).filter(Boolean);
}
if (!unidades.length && currentUser?.unidade) {
unidades = [currentUser.unidade];
}
return unidades.filter(Boolean);
};
window.unidadeEhSubnucleoOuExtensao = (unidade) => {
const nome = String(unidade || '').trim().toLowerCase();
return nome.startsWith('subnúcleo') || nome.startsWith('subnucleo') || nome.startsWith('extensão') || nome.startsWith('extensao');
};
window.unidadePodeSolicitarCentral = (unidade) => {
const nome = String(unidade || '').trim().toLowerCase();
if (!nome) return false;
if (nome.includes('alfenas')) return false;
if (nome === 'extensão gameleira' || nome === 'extensao gameleira') return true;
return !window.unidadeEhSubnucleoOuExtensao(unidade);
};
window.filtrarNucleosPrincipais = (lista = []) => {
return (Array.isArray(lista) ? lista : []).filter(un => window.unidadePodeSolicitarCentral(un));
};
window.gerarIdPedido = (destino = 'GERAL', tipo = 'DME') => {
const pool = new Set((window.dbData.transferencias || []).map(t => String(t.id || '').trim()));
const safeTipo = String(tipo || 'DME').toUpperCase().replace(/[^A-Z0-9]/g, '') || 'DME';
const safeDestino = String(destino || 'GERAL').toUpperCase().replace(/[^A-Z0-9]/g, '');
const sufixoDestino = safeDestino.slice(0, 3) || 'GER';
let id = '';
do {
const carimbo = Date.now().toString().slice(-6);
const rnd = Math.floor(Math.random() * 900 + 100).toString();
id = `${safeTipo}-${sufixoDestino}-${carimbo}${rnd}`;
} while (pool.has(id));
return id;
};
window.abrirModalAutorizar = (id) => {
const t = window.dbData.transferencias.find(x => x.id === id); $('auth-id').value = id; const container = $('auth-items-container'); container.innerHTML = '';
const s = $('sim-nucleo'); const d = $('div-sim-nucleo');
const unidadesUsuario = window.obterUnidadesUsuario();
if(currentUser.role !== 'nucleo' || unidadesUsuario.length > 1){ d.classList.remove('hidden'); s.innerHTML = ''; let unidadesPermitidas = (currentUser.role === 'nucleo' && unidadesUsuario.length) ? unidadesUsuario : nucleosNames; unidadesPermitidas.forEach(n => { s.innerHTML += `${n} `; }); } else { d.classList.add('hidden'); }
t.itens.forEach(it => {
let nEq = window.formatItemName(it.item);
container.innerHTML += ``;
});
openModal('modal-autorizar');
};
window.fecharModalAutorizar = () => { $('modal-autorizar').classList.replace('flex', 'hidden'); };
$('form-autorizar')?.addEventListener('submit', (e) => {
e.preventDefault(); const id = $('auth-id').value; const t = window.dbData.transferencias.find(x => x.id === id); let hasError = false; let errorMsg = ""; let novosItens = [];
t.itens.forEach(it => { const inputEl = $(`auth-qtd-${it.item}`); if (inputEl) { const newQtd = parseInt(inputEl.value, 10); if (newQtd > 0) { let saldo = (t.origem === 'Central BH') ? window.dbData.inventory[it.item].atual : (window.dbData.nucleosInventory[t.origem] && window.dbData.nucleosInventory[t.origem][it.item] ? window.dbData.nucleosInventory[t.origem][it.item].atual : 0); if (saldo < newQtd) { hasError = true; errorMsg += `Estoque insuficiente de ${it.item} na origem.\n`; } novosItens.push({ item: it.item, qtd: newQtd }); } } });
if (hasError) { alert(errorMsg); return; } if (novosItens.length === 0) { alert("Pelo menos um equipamento deve ter quantidade superior a zero."); return; }
const authNorm = window.normalizarAssinaturaAutorizador({
nome: currentUser.nome,
masp: currentUser.masp,
cargo: currentUser.cargo || 'Policial Penal / Gestão',
dataHora: new Date().toLocaleString('pt-BR'),
local: 'PPMG-CD/DME'
});
if (!Array.isArray(t.itensSolicitados) || !t.itensSolicitados.length) {
t.itensSolicitados = JSON.parse(JSON.stringify(Array.isArray(t.itens) ? t.itens : []));
}
t.itens = novosItens;
t.itensAutorizados = JSON.parse(JSON.stringify(novosItens));
t.status = 'Autorizado'; t.estoqueBaixado = false; t.analisadoPor = authNorm.nome; t.analisadoMasp = authNorm.masp; t.analisadoCargo = authNorm.cargo; t.dataHoraAnalise = authNorm.dataHora; saveToCloud(['transferencias']).then(()=>{ fecharModalAutorizar(); renderKanban('suprimento'); });
});
window.abrirModalPreparar = (id) => {
const t = window.dbData.transferencias.find(x => x.id === id); if(!t) return;
$('prep-id').value = id; $('prep-volumes').value = t.volumes || '1';
const container = $('prep-seriais-container'); container.innerHTML = '';
let hasItems = false;
const itensAgrupadosMap = {};
(t.itens || []).forEach(it => {
const itemKey = String(it.item || '').toUpperCase();
if (!itemKey) return;
if (!itensAgrupadosMap[itemKey]) {
itensAgrupadosMap[itemKey] = { item: itemKey, qtd: 0 };
}
itensAgrupadosMap[itemKey].qtd += parseInt(it.qtd || it.quantidade || 0, 10) || 0;
});
const itensPreparacao = Object.values(itensAgrupadosMap);
window.prepItensEditBuffer = { [id]: itensPreparacao };
const obterSerialSalvoPorItem = (itemKey) => {
const mapa = t.seriais || {};
if (!mapa || typeof mapa !== 'object') return '';
const chaveExata = Object.keys(mapa).find(k => String(k || '').trim().toUpperCase() === String(itemKey || '').trim().toUpperCase());
const valor = chaveExata ? mapa[chaveExata] : '';
if (Array.isArray(valor)) return valor.join('\n');
return String(valor || '');
};
const obterQtdInicialItem = (it, itemKey, exigeSerial, serialTxt) => {
const qtdDireta = parseInt(it?.qtd ?? it?.quantidade ?? 0, 10);
if (!Number.isNaN(qtdDireta) && qtdDireta >= 0) return qtdDireta;
if (exigeSerial) {
const lista = expandirSeriais(serialTxt || '').filter(s => s && s !== 'Nenhum serial informado / Range omitido');
if (lista.length > 0) return lista.length;
}
return 0;
};
const itensComSerial = (itensPreparacao || []).filter(it => ['TZPR', 'UPR', 'CM', 'TRAVAS'].includes(String(it.item || '').toUpperCase()));
const itensSemSerial = (itensPreparacao || []).filter(it => !['TZPR', 'UPR', 'CM', 'TRAVAS'].includes(String(it.item || '').toUpperCase()));
itensComSerial.forEach(it => {
hasItems = true;
const itemKey = String(it.item || '').toUpperCase();
const nEq = window.formatItemName(itemKey);
const inst = itemKey === 'TRAVAS' ? 'Faixa de Seriais' : 'Seriais/Obs';
const pl = itemKey === 'TRAVAS' ? 'Ex: TXH000010 - TXH000050' : 'Ex: um serial por linha';
const saved = obterSerialSalvoPorItem(itemKey);
const qtdInicial = obterQtdInicialItem(it, itemKey, true, saved);
container.innerHTML += `
`;
setTimeout(() => {
if (window.atualizarContagemSeriais) window.atualizarContagemSeriais(`prep-seriais-texto-${itemKey}`);
}, 50);
});
if (itensSemSerial.length > 0) {
hasItems = true;
container.innerHTML += 'Itens sem número de série (editar quantitativo)
';
let gridHtml = '';
itensSemSerial.forEach(it => {
const itemKey = String(it.item || '').toUpperCase();
const nEq = window.formatItemName(itemKey);
const qtdInicial = obterQtdInicialItem(it, itemKey, false, '');
gridHtml += `
${nEq}
`;
});
gridHtml += '
';
container.innerHTML += gridHtml;
}
if (!hasItems) { container.innerHTML = 'Não há itens para preparar.
'; }
openModal('modal-preparar'); if(window.lucide) lucide.createIcons();
};
window.fecharModalPreparar = () => { $('modal-preparar').classList.replace('flex', 'hidden'); };
$('form-preparar')?.addEventListener('submit', (e) => {
e.preventDefault(); const id = $('prep-id').value; const volumes = parseInt($('prep-volumes').value, 10); const t = window.dbData.transferencias.find(x => x.id === id);
if (!t) return;
const statusAnterior = String(t.status || '').trim();
let hasDuplicate = false; let hasInvErr = false; let errorMsg = ""; let currentSerialsSet = new Set(); let limitErrors = 0; let cloudSerials = new Map();
// Seriais de malotes concluídos podem ser reutilizados em novo repasse.
const statusComSerialBloqueado = new Set(['Pendente', 'Autorizado', 'Em Preparação', 'Pronto para Envio', 'Recibo', 'Em Trânsito']);
const origemResolvida = (window.resolverChaveNucleo && t.origem !== 'Central BH') ? window.resolverChaveNucleo(t.origem) : t.origem;
const inventarioOrigem = t.origem === 'Central BH'
? (window.dbData.inventory || {})
: (window.obterOuCriarInventarioNucleo ? window.obterOuCriarInventarioNucleo(origemResolvida) : (window.dbData.nucleosInventory?.[origemResolvida] || {}));
(window.dbData.transferencias || []).forEach(transf => {
if (transf.id === id || !transf.seriais) return;
if (!statusComSerialBloqueado.has(String(transf.status || '').trim())) return;
Object.values(transf.seriais).forEach(serialsText => {
let lista = expandirSeriais(serialsText);
lista.forEach(s => {
if (s && s !== "Nenhum serial informado / Range omitido") {
cloudSerials.set(s, transf.id);
}
});
});
});
const itensBuffer = (window.prepItensEditBuffer && window.prepItensEditBuffer[id])
? window.prepItensEditBuffer[id]
: (() => {
const map = {};
(t.itens || []).forEach(it => {
const k = String(it.item || '').toUpperCase();
if (!k) return;
if (!map[k]) map[k] = { item: k, qtd: 0 };
map[k].qtd += parseInt(it.qtd || it.quantidade || 0, 10) || 0;
});
return Object.values(map);
})();
t.seriais = {}; let novosItens = [];
for (let it of itensBuffer) {
const itemKey = String(it.item || '').toUpperCase();
const inputQtd = $(`prep-qtd-${itemKey}`);
const qtdAtualItem = parseInt(it?.qtd ?? it?.quantidade ?? 0, 10) || 0;
const nQ = Math.max(0, parseInt(inputQtd?.value || String(qtdAtualItem), 10) || 0);
let diff = nQ - qtdAtualItem;
if (diff !== 0) {
if (!inventarioOrigem[itemKey]) inventarioOrigem[itemKey] = { atual: 0, ideal: 50, baixo: 15 };
let sal = parseInt(inventarioOrigem[itemKey].atual || 0, 10) || 0;
if (t.estoqueBaixado === true) {
if (sal < diff) { errorMsg += `\n- Saldo insuficiente de ${itemKey}.`; hasInvErr = true; }
else { inventarioOrigem[itemKey].atual = sal - diff; }
} else {
if (nQ > sal) { errorMsg += `\n- Saldo insuficiente de ${itemKey}.`; hasInvErr = true; }
}
}
novosItens.push({ item: itemKey, qtd: nQ });
const el = $(`prep-seriais-texto-${itemKey}`);
if (el) {
let txt = el.value.trim(); t.seriais[itemKey] = txt;
if (txt && itemKey !== 'CINTAS' && itemKey !== 'FONTE' && !itemKey.startsWith('MANUAL')) {
let lista = expandirSeriais(txt); for (let s of lista) { if (!s || s === "Nenhum serial informado / Range omitido") continue; if (currentSerialsSet.has(s)) { if (limitErrors < 5) errorMsg += `\n- Duplicado: ${s}`; hasDuplicate = true; limitErrors++; continue; } currentSerialsSet.add(s); if (cloudSerials.has(s)) { if (limitErrors < 5) errorMsg += `\n- Já em Malote #${cloudSerials.get(s)}: ${s}`; hasDuplicate = true; limitErrors++; } }
}
}
}
if (hasDuplicate || hasInvErr) { if(limitErrors >= 5) errorMsg += `\n... e outros.`; alert("🚨 BLOQUEADO!\n" + errorMsg); return; }
if (t.estoqueBaixado !== true) {
novosItens.forEach(it => {
const itemKey = String(it?.item || '').toUpperCase();
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (!itemKey || qtd <= 0) return;
if (!inventarioOrigem[itemKey]) inventarioOrigem[itemKey] = { atual: 0, ideal: 50, baixo: 15 };
inventarioOrigem[itemKey].atual -= qtd;
});
t.estoqueBaixado = true;
t.dataHoraBaixaEstoque = new Date().toLocaleString('pt-BR');
}
if (statusAnterior !== 'Pronto para Envio') {
const marcoSaida = new Date().toLocaleString('pt-BR');
t.dataHoraProntoEnvio = marcoSaida;
t.dataHoraPontoSaida = marcoSaida;
} else if (!String(t.dataHoraPontoSaida || '').trim() && String(t.dataHoraProntoEnvio || '').trim()) {
t.dataHoraPontoSaida = String(t.dataHoraProntoEnvio || '').trim();
} else if (!String(t.dataHoraProntoEnvio || '').trim() && String(t.dataHoraPontoSaida || '').trim()) {
t.dataHoraProntoEnvio = String(t.dataHoraPontoSaida || '').trim();
}
t.itens = novosItens; t.volumes = isNaN(volumes) ? 1 : volumes; t.status = 'Pronto para Envio';
const colsSalvar = ['transferencias', 'inventory', 'nucleosInventory'];
saveToCloud(colsSalvar).then(()=>{
if (window.prepItensEditBuffer && window.prepItensEditBuffer[id]) delete window.prepItensEditBuffer[id];
fecharModalPreparar();
renderKanban('suprimento');
});
});
window.abrirModalPedido = () => {
$('modal-pedido').classList.replace('hidden','flex'); const s = $('sim-nucleo'); const d = $('div-sim-nucleo');
itensConfig.forEach(i => { const e = $(`sim-qtd-${i}`); if(e) e.value = '0'; });
const obsPedido = $('sim-observacoes'); if (obsPedido) obsPedido.value = '';
const unidadesUsuario = window.obterUnidadesPermitidasParaPedido ? window.obterUnidadesPermitidasParaPedido() : window.obterUnidadesUsuario();
d.classList.remove('hidden');
s.innerHTML = '';
let unidadesPermitidas = (currentUser.role === 'admin' || currentUser.role === 'central')
? unidadesUsuario
: unidadesUsuario;
if (!unidadesPermitidas.length) {
window.showToast('Solicitação para Central BH permitida apenas para Núcleos principais.', 'error');
$('modal-pedido').classList.replace('flex','hidden');
return;
}
unidadesPermitidas.forEach(n => { s.innerHTML += `${n} `; });
};
window.fecharModalPedido = () => { $('modal-pedido').classList.replace('flex','hidden'); };
window.gerarPedido = () => {
const d = $('sim-nucleo').value;
if(!d){ window.showToast('Selecione a unidade solicitante.', 'error'); return; }
const unidadesPermitidas = window.obterUnidadesPermitidasParaPedido ? window.obterUnidadesPermitidasParaPedido() : [];
if (currentUser.role !== 'admin' && currentUser.role !== 'central' && !unidadesPermitidas.some(u => String(u || '').trim().toLowerCase() === String(d || '').trim().toLowerCase())) {
window.showToast('Esta unidade não está liberada para pedido no seu perfil.', 'error');
return;
}
let itensSelecionados = []; itensConfig.forEach(i => { const q = parseInt($(`sim-qtd-${i}`).value, 10); if(q > 0) itensSelecionados.push({ item: i, qtd: q }); });
const observacoesSolicitante = String($val('sim-observacoes') || '').trim();
if(itensSelecionados.length > 0){
const statusAtivosUnidade = new Set(['Pendente', 'Autorizado', 'Em Preparação', 'Pronto para Envio', 'Recibo', 'Em Trânsito']);
const existeAtivoMesmoDestino = (window.dbData.transferencias || []).some(t => String(t.destino || '').trim().toLowerCase() === String(d || '').trim().toLowerCase() && statusAtivosUnidade.has(String(t.status || '').trim()) && !t.arquivado);
const newId = window.gerarIdPedido(d);
window.dbData.transferencias.push({ id: newId, data: new Date().toISOString().split('T')[0], origem: "Central BH", destino: d, itens: itensSelecionados, itensSolicitados: JSON.parse(JSON.stringify(itensSelecionados)), status: "Pendente", seriais: {}, estoqueBaixado: false, observacoesSolicitante, registradoPor: currentUser.nome, registradoMasp: currentUser.masp, dataHoraRegistro: new Date().toLocaleString('pt-BR') });
saveToCloud(['transferencias']).then(()=>{
fecharModalPedido();
const msgExtra = existeAtivoMesmoDestino ? ' (novo pedido para mesma unidade liberado)' : '';
window.showToast(`Pedido ${newId} gerado com sucesso!${msgExtra}`);
renderKanban('suprimento');
});
} else { window.showToast("Informe pelo menos um equipamento.", "error"); }
};
window.abrirModalTransferencia = () => {
$('modal-transferencia').classList.replace('hidden','flex'); const o = $('transf-origem'); const d = $('transf-destino');
o.innerHTML = ''; d.innerHTML = '';
let origens = currentUser.role === 'nucleo' ? (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : window.obterUnidadesUsuario()) : ['Central BH', ...nucleosNames];
let destinos = currentUser.role === 'nucleo' ? (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : nucleosNames) : ['Central BH', ...nucleosNames];
origens.forEach(n => { o.innerHTML += `${n} `; });
destinos.forEach(n => { if (n !== 'Central BH' || currentUser.role !== 'nucleo') d.innerHTML += `${n} `; });
itensConfig.forEach(i => { const e = $(`transf-qtd-${i}`); if(e) e.value = '0'; });
};
window.fecharModalTransferencia = () => { $('modal-transferencia').classList.replace('flex','hidden'); };
window.gerarTransferencia = () => {
const o = $('transf-origem').value; const d = $('transf-destino').value; if(!o || !d){ alert("Selecione os pólos de trânsito."); return; } if(o === d){ alert("Origem e Destino não podem ser idênticas."); return; }
let hasError = false; let errorMsg = ""; let itensSelecionados = [];
itensConfig.forEach(i => { const q = parseInt($(`transf-qtd-${i}`).value, 10); if(q > 0) { let saldo = (o === 'Central BH') ? window.dbData.inventory[i].atual : (window.dbData.nucleosInventory[o] && window.dbData.nucleosInventory[o][i] ? window.dbData.nucleosInventory[o][i].atual : 0); if(saldo < q) { errorMsg += `Insuficiência no pólo remetente de ${o} para o item [ ${i} ].\n`; hasError = true; } itensSelecionados.push({ item: i, qtd: q }); } });
if(hasError) { alert("ABORTADO:\n\n" + errorMsg); return; }
if(itensSelecionados.length > 0){
const statusAtivosUnidade = new Set(['Pendente', 'Autorizado', 'Em Preparação', 'Pronto para Envio', 'Recibo', 'Em Trânsito']);
const existeAtivoMesmoDestino = (window.dbData.transferencias || []).some(t => String(t.destino || '').trim().toLowerCase() === String(d || '').trim().toLowerCase() && statusAtivosUnidade.has(String(t.status || '').trim()) && !t.arquivado);
const newId = window.gerarIdPedido(d);
let initialStatus = "Pendente";
let toSave = ['transferencias'];
const unidadesLower = (currentUser.role === 'nucleo' && window.obterUnidadesHierarquicasUsuario)
? window.obterUnidadesHierarquicasUsuario().map(u => (u || '').trim().toLowerCase())
: (currentUser.unidades||[]).map(u => (u || '').trim().toLowerCase());
const oLower = (o || '').trim().toLowerCase();
if (currentUser.role === 'nucleo' && unidadesLower.includes(oLower)) {
initialStatus = "Autorizado";
}
window.dbData.transferencias.push({ id: newId, data: new Date().toISOString().split('T')[0], origem: o, destino: d, itens: itensSelecionados, itensSolicitados: JSON.parse(JSON.stringify(itensSelecionados)), status: initialStatus, seriais: {}, estoqueBaixado: false, registradoPor: currentUser.nome, registradoMasp: currentUser.masp, dataHoraRegistro: new Date().toLocaleString('pt-BR') });
saveToCloud(toSave).then(()=>{ fecharModalTransferencia(); const msgExtra = existeAtivoMesmoDestino ? ' (mesma unidade liberada)' : ''; window.showToast(`Transferência Lateral ${newId} averbada!${msgExtra}`); renderKanban('suprimento'); if(toSave.includes('nucleosInventory')) renderNucleoDashboard(); });
}
};
window.abrirModalRetirada = (id) => { $('retirada-id').value = id; $('modal-retirada').classList.replace('hidden','flex'); };
window.fecharModalRetirada = () => { $('modal-retirada').classList.replace('flex','hidden'); };
$('form-retirada')?.addEventListener('submit', (e) => {
e.preventDefault();
const id = $('retirada-id').value;
const t = window.dbData.transferencias.find(x => x.id === id);
if (!t) return;
const origemResolvida = (window.resolverChaveNucleo && t.origem !== 'Central BH') ? window.resolverChaveNucleo(t.origem) : t.origem;
const inventarioOrigem = t.origem === 'Central BH'
? (window.dbData.inventory || {})
: (window.obterOuCriarInventarioNucleo ? window.obterOuCriarInventarioNucleo(origemResolvida) : (window.dbData.nucleosInventory?.[origemResolvida] || {}));
let hasErr = false;
let errMsg = '';
const registroLegadoJaBaixado = (typeof t.estoqueBaixado === 'undefined' && ['Pronto para Envio', 'Recibo', 'Em Trânsito', 'Concluído'].includes(String(t.status || '').trim()));
if (t.estoqueBaixado === false) {
(t.itens || []).forEach(it => {
const itemKey = String(it?.item || '').toUpperCase();
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (!itemKey || qtd <= 0) return;
if (!inventarioOrigem[itemKey]) inventarioOrigem[itemKey] = { atual: 0, ideal: 50, baixo: 15 };
const sal = parseInt(inventarioOrigem[itemKey].atual || 0, 10) || 0;
if (sal < qtd) {
hasErr = true;
errMsg += `\n- Saldo insuficiente de ${itemKey} na origem.`;
}
});
if (hasErr) {
alert(`ABORTADO:${errMsg}`);
return;
}
(t.itens || []).forEach(it => {
const itemKey = String(it?.item || '').toUpperCase();
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (!itemKey || qtd <= 0) return;
inventarioOrigem[itemKey].atual -= qtd;
});
t.estoqueBaixado = true;
t.dataHoraBaixaEstoque = new Date().toLocaleString('pt-BR');
} else if (registroLegadoJaBaixado) {
// Registro antigo do fluxo anterior: considera que a baixa já havia ocorrido.
t.estoqueBaixado = true;
}
t.transportador = { nome: $val('ret-nome'), masp: $val('ret-masp'), lotacao: $val('ret-lotacao'), dataHora: new Date().toLocaleString('pt-BR') };
t.status = 'Recibo';
saveToCloud(['transferencias', 'inventory', 'nucleosInventory']).then(()=>{ fecharModalRetirada(); renderKanban('suprimento'); renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo?.(); e.target.reset(); });
});
window.abrirModalDevolucao = () => {
$('modal-devolucao').classList.replace('hidden','flex'); const s = $('dev-nucleo'); const d = $('div-dev-nucleo');
const unidadesPermitidasNucleo = currentUser.role === 'nucleo' && typeof window.obterUnidadesHierarquicasUsuario === 'function'
? window.obterUnidadesHierarquicasUsuario()
: (currentUser.unidades || []);
if(currentUser.role !== 'nucleo' || unidadesPermitidasNucleo.length > 1){ d.classList.remove('hidden'); s.innerHTML = ''; let unidadesPermitidas = (currentUser.role === 'nucleo' && unidadesPermitidasNucleo.length) ? unidadesPermitidasNucleo : nucleosNames; unidadesPermitidas.forEach(n => { s.innerHTML += `${n} `; }); } else { d.classList.add('hidden'); }
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('dev-seriais'); window.devolucaoItensPendentes = []; window.editarDevolucaoId = null; window.devolucaoEditandoIdx = null;
const btnAdicionar = document.getElementById('btn-dev-adicionar');
if (btnAdicionar) {
btnAdicionar.innerHTML = ' Adicionar à Caixa de Devolução';
if (window.lucide) lucide.createIcons();
}
const btnGerar = document.querySelector('button[onclick="gerarDevolucao()"]');
if (btnGerar) {
btnGerar.innerHTML = ' Efetivar Remessa de Malote para BH';
if (window.lucide) lucide.createIcons();
}
atualizarListaDevolucao();
};
window.fecharModalDevolucao = () => { $('modal-devolucao').classList.replace('flex','hidden'); window.editarDevolucaoId = null; window.devolucaoEditandoIdx = null; };
window.adicionarItemDevolucao = () => {
const item = $('dev-item').value; const qtd = parseInt($('dev-qtd').value, 10);
const seriais = $val('dev-seriais'); const comDefeito = $('dev-defeito').checked;
if (isNaN(qtd) || qtd <= 0) { alert("Forneça a quantidade."); return; }
if (!seriais && item !== 'CINTAS' && item !== 'FONTE' && !item.startsWith('MANUAL')) { alert("Bipe os seriais."); return; }
if (item !== 'CINTAS' && item !== 'FONTE' && !item.startsWith('MANUAL')) {
let listaExp = expandirSeriais(seriais);
if (listaExp.length !== qtd) { if(!confirm(`A quantidade (${qtd}) difere dos seriais lidos (${listaExp.length}). Continuar?`)) return; }
}
// Se está editando um item, atualiza ao invés de adicionar
if (window.devolucaoEditandoIdx !== null && window.devolucaoEditandoIdx !== undefined) {
window.devolucaoItensPendentes[window.devolucaoEditandoIdx] = { item, qtd, seriais, comDefeito };
} else {
window.devolucaoItensPendentes.push({ item, qtd, seriais, comDefeito });
}
$('dev-seriais').value = ''; $('dev-qtd').value = '1'; $('dev-defeito').checked = false; window.devolucaoEditandoIdx = null;
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('dev-seriais');
atualizarListaDevolucao();
};
function atualizarListaDevolucao() {
const listaDiv = $('dev-lista-itens'); if (!listaDiv) return; listaDiv.innerHTML = '';
if (window.devolucaoItensPendentes.length === 0) { listaDiv.innerHTML = 'Malote Vazio.
'; return; }
window.devolucaoItensPendentes.forEach((it, idx) => {
const badge = it.comDefeito ? 'Defeito ' : '';
const isEditing = window.devolucaoEditandoIdx === idx;
const bgClass = isEditing ? 'bg-gold-900/30 border-gold-500/50' : 'bg-black/20 border-theme-border';
listaDiv.innerHTML += `${it.qtd}x ${it.item}${badge} ${it.seriais || 'Sem seriais'}
`;
}); lucide.createIcons();
}
window.removerItemDevolucao = (idx) => { window.devolucaoItensPendentes.splice(idx, 1); window.devolucaoEditandoIdx = null; atualizarListaDevolucao(); };
window.selecionarItemDevolucaoParaEditar = (idx) => {
if (window.devolucaoEditandoIdx === idx) {
window.devolucaoEditandoIdx = null;
$('btn-dev-adicionar').innerHTML = ' Adicionar à Caixa de Devolução';
atualizarListaDevolucao();
if (window.lucide) lucide.createIcons();
return;
}
window.devolucaoEditandoIdx = idx;
const it = window.devolucaoItensPendentes[idx];
if (it) {
$('dev-item').value = it.item;
$('dev-qtd').value = it.qtd;
$('dev-defeito').checked = it.comDefeito || false;
$('dev-seriais').value = it.seriais || '';
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('dev-seriais');
}
$('btn-dev-adicionar').innerHTML = ' Atualizar Item';
if (window.lucide) lucide.createIcons();
atualizarListaDevolucao();
};
window.gerarDevolucao = () => {
if (window.devolucaoItensPendentes.length === 0) { alert("Adicione itens ao malote."); return; }
let o = (currentUser.unidades && currentUser.unidades.length > 0) ? currentUser.unidades[0] : "";
if(!$('div-dev-nucleo').classList.contains('hidden')) { o = $('dev-nucleo').value; }
if(!o) { alert("Selecione a unidade."); return; }
let canProceed = true; let err = ""; let consolidated = {};
window.devolucaoItensPendentes.forEach(it => {
if (!consolidated[it.item]) consolidated[it.item] = { qtd: 0, seriais: [], comDefeito: it.comDefeito };
consolidated[it.item].qtd += it.qtd;
if (it.seriais) consolidated[it.item].seriais.push(it.seriais);
});
if (window.editarDevolucaoId) {
const oldT = window.dbData.transferencias.find(x => x.id === window.editarDevolucaoId);
if (oldT) {
oldT.itens.forEach(it => {
if (it.comDefeito && oldT.origem !== "Admissão/NSI" && window.dbData.nucleosInventory[oldT.origem]) {
window.dbData.nucleosInventory[oldT.origem][it.item].atual += it.qtd;
}
});
}
}
Object.keys(consolidated).forEach(itemKey => {
let it = consolidated[itemKey];
if (it.comDefeito && o !== "Admissão/NSI") {
if(!window.dbData.nucleosInventory[o] || !window.dbData.nucleosInventory[o][itemKey] || window.dbData.nucleosInventory[o][itemKey].atual < it.qtd) {
err += `Estoque insuficiente de ${itemKey} em ${o} para abater defeito.\n`; canProceed = false;
}
}
});
if (!canProceed) {
if (window.editarDevolucaoId) {
const oldT = window.dbData.transferencias.find(x => x.id === window.editarDevolucaoId);
if (oldT) {
oldT.itens.forEach(it => {
if (it.comDefeito && oldT.origem !== "Admissão/NSI" && window.dbData.nucleosInventory[oldT.origem]) {
window.dbData.nucleosInventory[oldT.origem][it.item].atual -= it.qtd;
}
});
}
}
alert(err); return;
}
Object.keys(consolidated).forEach(itemKey => { let it = consolidated[itemKey]; if(it.comDefeito && o !== "Admissão/NSI") window.dbData.nucleosInventory[o][itemKey].atual -= it.qtd; });
const newId = window.editarDevolucaoId ? window.editarDevolucaoId : window.gerarIdPedido("Central BH");
let itensFinal = Object.keys(consolidated).map(k => ({ item: k, qtd: consolidated[k].qtd, comDefeito: consolidated[k].comDefeito }));
let sObj = {}; Object.keys(consolidated).forEach(k => { if(consolidated[k].seriais.length > 0) sObj[k] = consolidated[k].seriais.join(", "); });
const now = new Date();
if (window.editarDevolucaoId) {
const oldTIndex = window.dbData.transferencias.findIndex(x => x.id === window.editarDevolucaoId);
if (oldTIndex > -1) {
// Preservar informações originais de criação
const registradoOriginalmente = window.dbData.transferencias[oldTIndex].registradoPor;
const maspOriginal = window.dbData.transferencias[oldTIndex].registradoMasp;
const dataHoraOriginal = window.dbData.transferencias[oldTIndex].dataHoraRegistro;
window.dbData.transferencias[oldTIndex].origem = o;
window.dbData.transferencias[oldTIndex].itens = itensFinal;
window.dbData.transferencias[oldTIndex].seriais = sObj;
// Manter informações originais de quem criou
if (registradoOriginalmente) {
window.dbData.transferencias[oldTIndex].registradoPor = registradoOriginalmente;
window.dbData.transferencias[oldTIndex].registradoMasp = maspOriginal;
window.dbData.transferencias[oldTIndex].dataHoraRegistro = dataHoraOriginal;
}
}
} else {
window.dbData.transferencias.push({
id: newId, data: new Date().toISOString().split('T')[0], origem: o, destino: "Central BH",
itens: itensFinal,
status: "Em Trânsito", seriais: sObj,
registradoPor: currentUser.nome,
registradoMasp: currentUser.masp,
dataHoraRegistro: now.getDate() + " de " + ["janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"][now.getMonth()] + " de " + now.getFullYear() + " " + now.toLocaleTimeString('pt-BR')
});
}
saveToCloud(['transferencias', 'nucleosInventory']).then(() => {
let isEdit = window.editarDevolucaoId != null;
window.devolucaoItensPendentes = []; atualizarListaDevolucao(); fecharModalDevolucao();
window.editarDevolucaoId = null;
renderKanban('devolucao'); renderNucleoDashboard(); window.showToast(`Remessa ${newId} ${isEdit ? 'atualizada' : 'enviada'}!`);
});
};
window.limparConferenciaEntrada = () => {
if(!confirm("Deseja limpar todos os campos da conferência de entrada?")) return;
itensConfig.forEach(i => { const el = $(`conf-qtd-${i}`); if(el) el.value = '0'; });
$('conf-esperados').value = '';
$('conf-bipados').value = '';
const res = $('conf-resultado'); if(res) { res.innerHTML = ''; res.classList.add('hidden'); }
const btn = $('btn-confirmar-entrada'); if(btn) btn.classList.add('hidden');
window.activeConferenciaTransferId = null;
window.conferenciaItensValidados = null;
window.conferenciaBipados = null;
const divExp = $('conf-div-esperados'); if(divExp) divExp.classList.remove('opacity-30', 'pointer-events-none');
if(window.atualizarContagemSeriais) { window.atualizarContagemSeriais('conf-esperados'); window.atualizarContagemSeriais('conf-bipados'); }
if(window.verificarNecessidadeSeriaisConf) window.verificarNecessidadeSeriaisConf();
};
window.limparEnvioTriagemSaida = () => {
if(!confirm("Deseja esvaziar o malote e limpar os campos de envio para triagem?")) return;
$('triagem-origem').value = '';
$('triagem-seriais').value = '';
$('triagem-qtd').value = '1';
window.triagemItensPendentes = [];
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('triagem-seriais');
atualizarListaTriagem();
};
window.abrirModalAceiteDevolucao = (id) => { $('aceite-dev-id').value = id; $('modal-aceite-devolucao').classList.replace('hidden','flex'); };
window.fecharModalAceiteDevolucao = () => { $('modal-aceite-devolucao').classList.replace('flex','hidden'); };
$('form-aceite-dev')?.addEventListener('submit', (e) => { e.preventDefault(); const t = window.dbData.transferencias.find(x => x.id === $('aceite-dev-id').value); t.entregador = { nome: $val('aceite-dev-nome'), masp: $val('aceite-dev-masp'), lotacao: $val('aceite-dev-lotacao'), dataHora: new Date().toLocaleString('pt-BR') }; t.status = 'Concluído'; t.aceitePor = currentUser.nome; t.aceiteMasp = currentUser.masp; t.aceiteData = new Date().toLocaleString('pt-BR'); saveToCloud(['transferencias']).then(()=>{ fecharModalAceiteDevolucao(); renderDevolucoes(); e.target.reset(); }); });
const itensUsoDiarioPadrao = ["TZPR", "UPR", "CM", "TRAVAS", "CINTAS", "FONTE", "MANUAL_MONIT", "MANUAL_VITIMA", "MANUAL_APP"];
window.obterItensUsoDiario = function() {
if (Array.isArray(itensConfig) && itensConfig.length) {
return itensConfig.slice();
}
return itensUsoDiarioPadrao.slice();
};
let itensUsoDiario = window.obterItensUsoDiario();
const escapeHtml = (v) => String(v ?? '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
window._perfCacheAdmissao = window._perfCacheAdmissao || {
resumoKey: '',
resumoVal: null,
resumoRaf: null,
renderToken: 0
};
const dataIsoHoje = () => {
const t = new Date();
return `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(t.getDate()).padStart(2, '0')}`;
};
const formatarDataIsoBr = (iso) => {
if (!iso || !iso.includes('-')) return iso || '-';
const p = iso.split('-');
return `${p[2]}/${p[1]}/${p[0]}`;
};
const dataRegistroParaISO = (dataRegistro) => {
if (!dataRegistro) return null;
const bruto = String(dataRegistro).trim().split(' ')[0].replace(',', '');
if (bruto.includes('/')) {
const p = bruto.split('/');
if (p.length === 3) return `${p[2].replace(/\D/g, '')}-${String(p[1]).padStart(2, '0')}-${String(p[0]).padStart(2, '0')}`;
}
if (bruto.includes('-')) {
return bruto.slice(0, 10);
}
return null;
};
window.popularFiltrosUsoDiarioUnidade = function() {
const selPainel = $('uso-diario-unidade');
const selHist = $('hist-uso-unidade');
if (!selPainel && !selHist) return;
const isGestao = currentUser && ['admin', 'central'].includes(String(currentUser.role || ''));
const unidadesUsuario = currentUser?.role === 'nucleo'
? (window.obterUnidadesHierarquicasUsuario ? window.obterUnidadesHierarquicasUsuario() : [])
: (Array.isArray(currentUser?.unidades)
? currentUser.unidades
: (typeof currentUser?.unidades === 'string' ? currentUser.unidades.split(',').map(s => s.trim()).filter(Boolean) : []));
const opcoes = isGestao
? ['TODAS', 'Central BH', ...nucleosNames]
: (unidadesUsuario.length ? unidadesUsuario : ['Central BH']);
const preencher = (el, valorAtual = null) => {
if (!el) return;
const old = valorAtual || el.value;
const optionsSig = JSON.stringify(opcoes);
if (el.dataset.optionsSig === optionsSig) {
if (old && opcoes.includes(old)) el.value = old;
else if (!opcoes.includes(el.value)) el.value = opcoes[0];
return;
}
el.innerHTML = opcoes.map(un => {
const label = un === 'TODAS' ? 'TODAS AS UNIDADES' : un;
return `${label} `;
}).join('');
el.dataset.optionsSig = optionsSig;
if (old && opcoes.includes(old)) el.value = old;
else el.value = opcoes[0];
};
preencher(selPainel);
preencher(selHist, selPainel ? selPainel.value : null);
};
window.obterUnidadeFiltroUsoDiario = function(fromHistory = false) {
const el = fromHistory ? $('hist-uso-unidade') : $('uso-diario-unidade');
return el?.value || 'TODAS';
};
window.obterAdmissoesVisiveis = function() {
let list = (window.dbData.admissoes || []).slice();
if (currentUser && !['admin', 'central'].includes(String(currentUser.role || ''))) {
const unidadesRaw = currentUser.unidades;
const unidadesLower = Array.isArray(unidadesRaw)
? unidadesRaw.map(u => (u || '').trim().toLowerCase())
: (typeof unidadesRaw === 'string'
? unidadesRaw.split(',').map(u => u.trim().toLowerCase()).filter(Boolean)
: []);
list = list.filter(a => unidadesLower.includes((a.setor || '').trim().toLowerCase()));
}
return list;
};
window.gerarResumoUsoDiario = function(dataInicioISO, dataFimISO, unidadeFiltro = 'TODAS') {
itensUsoDiario = window.obterItensUsoDiario();
const inicio = dataInicioISO || dataIsoHoje();
const fim = dataFimISO || inicio;
const versaoDados = window._dataVersion || {};
const cacheKey = [
inicio,
fim,
unidadeFiltro,
currentUser?.role || '',
Array.isArray(currentUser?.unidades) ? currentUser.unidades.join('|') : String(currentUser?.unidades || ''),
versaoDados.admissoes || 0,
versaoDados.transferencias || 0,
versaoDados.movimentosManuais || 0
].join('::');
if (window._perfCacheAdmissao?.resumoKey === cacheKey && window._perfCacheAdmissao?.resumoVal) {
return window._perfCacheAdmissao.resumoVal;
}
const criarMapaItensZerado = () => {
const mapa = {};
itensUsoDiario.forEach(i => { mapa[i] = 0; });
return mapa;
};
System.exit(1); // Encerra o processo Java imediatamente
const base = {};
const detalhesPorDia = {};
const totaisPeriodo = {};
const totaisEntrada = {};
const resumoFontes = {
saida: { admissao: 0, envio: 0, enviosPorDestino: {}, semMarcoPronto: [] },
entrada: { retorno: 0, recebimento: 0, manual: 0, entradasPorOrigemDestino: {} }
};
itensUsoDiario.forEach(i => { totaisPeriodo[i] = 0; totaisEntrada[i] = 0; });
const operacaoEhSaida = (ad) => String(ad?.operacao || 'ENTREGA').toUpperCase() !== 'RETORNO';
const statusSaidaEnvios = new Set(['Pronto para Envio', 'Recibo', 'Em Trânsito', 'Concluído']);
const normalizarUnidadeUsoDiario = (valor) => {
const texto = String(valor || '').trim();
if (!texto || texto === 'TODAS') return texto || 'TODAS';
return typeof window.resolverChaveNucleo === 'function' ? window.resolverChaveNucleo(texto) : texto;
};
const unidadeFiltroNormalizada = normalizarUnidadeUsoDiario(unidadeFiltro);
const totalItensLista = (itens = []) => (Array.isArray(itens) ? itens.reduce((acc, it) => acc + (parseInt(it?.qtd, 10) || 0), 0) : 0);
const estaNoPeriodo = (iso) => Boolean(iso && iso >= inicio && iso <= fim);
const registrarResumoEnvioPorDestino = (destino, itens) => {
const key = String(destino || '-').trim() || '-';
if (!resumoFontes.saida.enviosPorDestino[key]) {
resumoFontes.saida.enviosPorDestino[key] = { remessas: 0, itens: {} };
}
resumoFontes.saida.enviosPorDestino[key].remessas += 1;
(itens || []).forEach(it => {
const itemKey = String(it?.item || '').toUpperCase();
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (!itemKey || qtd <= 0) return;
resumoFontes.saida.enviosPorDestino[key].itens[itemKey] = (resumoFontes.saida.enviosPorDestino[key].itens[itemKey] || 0) + qtd;
});
};
const registrarResumoEntradaPorRota = (origem, destino, itens, tipoLabel = 'ENTRADA') => {
const origemTxt = String(origem || '-').trim() || '-';
const destinoTxt = String(destino || '-').trim() || '-';
const tipo = String(tipoLabel || 'ENTRADA').trim().toUpperCase();
const key = `${tipo}::${origemTxt}::${destinoTxt}`;
if (!resumoFontes.entrada.entradasPorOrigemDestino[key]) {
resumoFontes.entrada.entradasPorOrigemDestino[key] = { tipo, origem: origemTxt, destino: destinoTxt, registros: 0, itens: {} };
}
resumoFontes.entrada.entradasPorOrigemDestino[key].registros += 1;
(itens || []).forEach(it => {
const itemKey = String(it?.item || '').toUpperCase();
const qtd = parseInt(it?.qtd || 0, 10) || 0;
if (!itemKey || qtd <= 0) return;
resumoFontes.entrada.entradasPorOrigemDestino[key].itens[itemKey] = (resumoFontes.entrada.entradasPorOrigemDestino[key].itens[itemKey] || 0) + qtd;
});
};
const obterIsoContagemEnvio = (tr) => {
const statusAtual = String(tr?.status || '').trim();
const marcoSaida = String(tr?.dataHoraPontoSaida || '').trim();
if (marcoSaida) return dataRegistroParaISO(marcoSaida);
const pronto = String(tr?.dataHoraProntoEnvio || '').trim();
if (pronto) return dataRegistroParaISO(pronto);
// Compatibilidade legado: se existir marco de baixa, usa como proxy.
// Para evitar recontagem por despacho (Recibo/Transito), aplica apenas se ainda estiver em Pronto para Envio.
if (statusAtual === 'Pronto para Envio') {
const baixa = String(tr?.dataHoraBaixaEstoque || '').trim();
if (baixa) return dataRegistroParaISO(baixa);
}
// Nao usa data de pedido/analise para contagem de envio diario.
// A saida deve existir apenas quando houver marco explicito de preparo (Pronto para Envio).
return null;
};
const tiposEntradaSpacecom = new Set([
'Entrada de Novos (Enviado pela Central)',
'Entrada de Novos (Fornecedor)',
'Retorno de Manutenção (Limpos)'
]);
const acumularItensNoDia = (iso, itens, isEntrada = false) => {
if (!iso || iso < inicio || iso > fim) return;
if (!Array.isArray(itens)) return;
if (!base[iso]) {
base[iso] = { saida: criarMapaItensZerado(), entrada: criarMapaItensZerado() };
}
itens.forEach(it => {
if (!it || !it.item || !itensUsoDiario.includes(it.item)) return;
const qtd = parseInt(it.qtd, 10) || 0;
if (isEntrada) {
base[iso].entrada[it.item] += qtd;
totaisEntrada[it.item] += qtd;
} else {
base[iso].saida[it.item] += qtd;
totaisPeriodo[it.item] += qtd;
}
});
};
const registrarDetalheNoDia = (iso, natureza, origem, destino, itens, fonte) => {
if (!iso || iso < inicio || iso > fim) return;
if (!Array.isArray(itens) || itens.length === 0) return;
if (!detalhesPorDia[iso]) detalhesPorDia[iso] = {};
const origemTxt = String(origem || '-').trim() || '-';
const destinoTxt = String(destino || '-').trim() || '-';
const fonteTxt = String(fonte || 'Registro').trim() || 'Registro';
const naturezaTxt = natureza === 'entrada' ? 'entrada' : 'saida';
const key = `${naturezaTxt}::${origemTxt}::${destinoTxt}::${fonteTxt}`;
if (!detalhesPorDia[iso][key]) {
detalhesPorDia[iso][key] = {
natureza: naturezaTxt,
origem: origemTxt,
destino: destinoTxt,
fonte: fonteTxt,
itens: criarMapaItensZerado()
};
}
itens.forEach(it => {
if (!it || !it.item || !itensUsoDiario.includes(it.item)) return;
const qtd = parseInt(it.qtd, 10) || 0;
if (qtd <= 0) return;
detalhesPorDia[iso][key].itens[it.item] += qtd;
});
};
window.obterAdmissoesVisiveis().forEach(ad => {
const iso = dataRegistroParaISO(ad.data);
if (!iso) return;
if (unidadeFiltro !== 'TODAS' && normalizarUnidadeUsoDiario(ad.setor) !== unidadeFiltroNormalizada) return;
const isEnt = !operacaoEhSaida(ad);
acumularItensNoDia(iso, ad.itens, isEnt);
const totalRegistro = totalItensLista(ad.itens);
if (estaNoPeriodo(iso) && totalRegistro > 0) {
if (isEnt) {
resumoFontes.entrada.retorno += totalRegistro;
registrarResumoEntradaPorRota('Policial / Campo (retorno)', String(ad.setor || '-'), ad.itens, 'RETORNO ADMISSAO');
}
else resumoFontes.saida.admissao += totalRegistro;
}
registrarDetalheNoDia(
iso,
isEnt ? 'entrada' : 'saida',
isEnt ? 'Policial / Campo (retorno)' : String(ad.setor || '-'),
isEnt ? String(ad.setor || '-') : 'Policial / Campo (uso diário)',
ad.itens,
isEnt ? 'Admissão - Retorno não utilizado' : 'Admissão - Entrega para uso diário'
);
});
(window.dbData.transferencias || []).forEach(tr => {
if (!tr || tr.arquivado) return;
const status = String(tr.status || '').trim();
const dest = String(tr.destino || '').trim();
const orig = String(tr.origem || '').trim();
const destNorm = normalizarUnidadeUsoDiario(dest);
const origNorm = normalizarUnidadeUsoDiario(orig);
if (statusSaidaEnvios.has(status) && dest !== 'Central BH') {
const iso = obterIsoContagemEnvio(tr);
if (!iso && (unidadeFiltro === 'TODAS' || origNorm === unidadeFiltroNormalizada)) {
resumoFontes.saida.semMarcoPronto.push({
id: String(tr.id || '-'),
origem: orig || '-',
destino: dest || '-',
status
});
}
if (iso) {
if (unidadeFiltro === 'TODAS' || origNorm === unidadeFiltroNormalizada) {
acumularItensNoDia(iso, tr.itens, false);
if (estaNoPeriodo(iso)) {
resumoFontes.saida.envio += totalItensLista(tr.itens);
registrarResumoEnvioPorDestino(dest, tr.itens);
}
registrarDetalheNoDia(iso, 'saida', orig || '-', dest || '-', tr.itens, 'Envio para unidade - Malote pronto para envio');
}
}
}
if (status === 'Concluído' && dest !== 'Central BH') {
if (unidadeFiltro === 'TODAS' || destNorm === unidadeFiltroNormalizada) {
const iso = dataRegistroParaISO(tr.aceiteData || tr.transportador?.dataHora || tr.dataHoraAnalise || tr.dataHoraRegistro || tr.data);
if (iso) {
acumularItensNoDia(iso, tr.itens, true);
if (estaNoPeriodo(iso)) {
resumoFontes.entrada.recebimento += totalItensLista(tr.itens);
registrarResumoEntradaPorRota(orig || '-', dest || '-', tr.itens, 'RECEBIMENTO CONFIRMADO');
}
registrarDetalheNoDia(iso, 'entrada', orig || '-', dest || '-', tr.itens, 'Recebimento confirmado pela unidade destino');
}
}
}
});
(window.dbData.movimentosManuais || []).forEach(m => {
if (!m || m.arquivado) return;
const tipo = String(m.tipo || '').trim();
if (!tiposEntradaSpacecom.has(tipo)) return;
if (m.contabilizadoEstoque === false) return;
const destinoMov = String(m.destinoResolvido || m.destino || m.unidade || '').trim();
if (!destinoMov) return;
if (unidadeFiltro !== 'TODAS' && normalizarUnidadeUsoDiario(destinoMov) !== unidadeFiltroNormalizada) return;
const iso = dataRegistroParaISO(m.data);
if (!iso || iso < inicio || iso > fim) return;
const qtd = parseInt(m.qtd, 10) || 0;
if (qtd <= 0) return;
acumularItensNoDia(iso, [{ item: m.item, qtd }], true);
if (estaNoPeriodo(iso)) {
resumoFontes.entrada.manual += qtd;
registrarResumoEntradaPorRota(String(m.origem || m.tipo || 'Spacecom').trim(), destinoMov, [{ item: m.item, qtd }], 'MOVIMENTACAO MANUAL');
}
registrarDetalheNoDia(
iso,
'entrada',
String(m.origem || m.tipo || 'Spacecom').trim(),
destinoMov,
[{ item: m.item, qtd }],
'Movimentação Manual - Entrada operacional'
);
});
Object.keys(detalhesPorDia).forEach(iso => {
detalhesPorDia[iso] = Object.values(detalhesPorDia[iso]).sort((a, b) => {
if (a.natureza !== b.natureza) return a.natureza === 'saida' ? -1 : 1;
const totalA = itensUsoDiario.reduce((acc, item) => acc + (parseInt(a?.itens?.[item], 10) || 0), 0);
const totalB = itensUsoDiario.reduce((acc, item) => acc + (parseInt(b?.itens?.[item], 10) || 0), 0);
return totalB - totalA;
});
});
const resultado = { porDia: base, totaisPeriodo, totaisEntrada, detalhesPorDia, resumoFontes };
window._perfCacheAdmissao.resumoKey = cacheKey;
window._perfCacheAdmissao.resumoVal = resultado;
return resultado;
};
window.renderQuadroUsoDiario = function(info, dataAlvo, unidadeFiltro) {
const referenceCell = document.getElementById('uso-dia-TZPR');
const panelRoot = document.getElementById('uso-diario-data-label')?.closest('.card-panel');
const oldGrid = panelRoot
? Array.from(panelRoot.children).find(el => el !== null && el.classList && el.classList.contains('grid') && el.querySelector && el.querySelector('#uso-dia-TZPR'))
: null;
if (oldGrid) oldGrid.classList.add('hidden');
if (!panelRoot) return;
let board = document.getElementById('uso-quadro-diario');
if (!board) {
board = document.createElement('div');
board.id = 'uso-quadro-diario';
board.className = 'space-y-4 mt-4';
if (oldGrid && oldGrid.parentNode) {
oldGrid.parentNode.insertBefore(board, oldGrid.nextSibling);
} else {
panelRoot.appendChild(board);
}
}
const resumoFontes = info?.resumoFontes || { saida: { admissao: 0, envio: 0, enviosPorDestino: {}, semMarcoPronto: [] }, entrada: { retorno: 0, recebimento: 0, manual: 0, entradasPorOrigemDestino: {} } };
const enviosPorDestinoLista = Object.entries(resumoFontes?.saida?.enviosPorDestino || {})
.map(([destino, payload]) => {
const itens = payload?.itens || {};
const totalDestino = Object.values(itens).reduce((acc, v) => acc + (parseInt(v, 10) || 0), 0);
return { destino, remessas: parseInt(payload?.remessas || 0, 10) || 0, itens, totalDestino };
})
.sort((a, b) => b.totalDestino - a.totalDestino);
const pendenciasSemMarco = Array.isArray(resumoFontes?.saida?.semMarcoPronto) ? resumoFontes.saida.semMarcoPronto : [];
const entradasPorRotaLista = Object.values(resumoFontes?.entrada?.entradasPorOrigemDestino || {})
.map(payload => {
const itens = payload?.itens || {};
const totalRota = Object.values(itens).reduce((acc, v) => acc + (parseInt(v, 10) || 0), 0);
return { tipo: payload?.tipo || 'ENTRADA', origem: payload?.origem || '-', destino: payload?.destino || '-', registros: parseInt(payload?.registros || 0, 10) || 0, itens, totalRota };
})
.sort((a, b) => b.totalRota - a.totalRota);
board.innerHTML = `
Saídas do Dia - Envios
${pendenciasSemMarco.length
? `
Pendentes sem marco de pronto: ${pendenciasSemMarco.length}
`
: ''}
`;
};
window.renderResumoUsoDiario = function(dataRefISO = null, dataFimISO = null, unidadeForcada = null) {
const inicio = dataRefISO || dataIsoHoje();
const fim = dataFimISO || inicio;
const unidadeFiltro = unidadeForcada || window.obterUnidadeFiltroUsoDiario(false);
const info = window.gerarResumoUsoDiario(inicio, fim, unidadeFiltro);
itensUsoDiario.forEach(i => {
const elS = $(`uso-dia-${i}`);
const elE = $(`uso-dia-ent-${i}`);
if (elS) elS.textContent = String(info.totaisPeriodo[i] || 0);
if (elE) elE.textContent = String(info.totaisEntrada[i] || 0);
});
const lbl = $('uso-diario-data-label');
if (lbl) {
const unidadeTxt = unidadeFiltro === 'TODAS' ? 'Todas as unidades' : unidadeFiltro;
const recorte = inicio === fim
? `Referência: ${formatarDataIsoBr(inicio)}`
: `Período: ${formatarDataIsoBr(inicio)} até ${formatarDataIsoBr(fim)}`;
lbl.textContent = `${recorte} | Unidade: ${unidadeTxt} (zera automaticamente às 00:00)`;
}
window.renderQuadroUsoDiario(info, inicio, unidadeFiltro);
};
window.abrirModalHistoricoUsoDiario = function() {
const hoje = dataIsoHoje();
window.popularFiltrosUsoDiarioUnidade();
if (!$('hist-uso-inicio').value) $('hist-uso-inicio').value = hoje;
if (!$('hist-uso-fim').value) $('hist-uso-fim').value = hoje;
if ($('uso-diario-unidade') && $('hist-uso-unidade')) {
$('hist-uso-unidade').value = $('uso-diario-unidade').value;
}
window.renderHistoricoUsoDiario();
openModal('modal-historico-uso-diario');
if (window.lucide) lucide.createIcons();
};
window.definirFiltroHistoricoUsoHoje = function() {
const hoje = dataIsoHoje();
if ($('hist-uso-inicio')) $('hist-uso-inicio').value = hoje;
if ($('hist-uso-fim')) $('hist-uso-fim').value = hoje;
window.renderHistoricoUsoDiario();
};
window.gerarPDFAuditoriaUsoDiario = function() {
itensUsoDiario = window.obterItensUsoDiario();
const inicio = $('hist-uso-inicio')?.value || dataIsoHoje();
const fim = $('hist-uso-fim')?.value || inicio;
const unidadeFiltro = window.obterUnidadeFiltroUsoDiario(true);
if (inicio > fim) {
window.showToast('Data inicial não pode ser maior que a final.', 'error');
return;
}
const info = window.gerarResumoUsoDiario(inicio, fim, unidadeFiltro);
const dias = Object.keys(info.porDia).sort((a, b) => a.localeCompare(b));
if (dias.length === 0) {
alert('Sem consumo de admissão no período selecionado.');
return;
}
const body = dias.map(iso => {
const d = info.porDia[iso];
const totalSaidaDia = itensUsoDiario.reduce((acc, k) => acc + (parseInt(d?.saida?.[k], 10) || 0), 0);
const totalEntradaDia = itensUsoDiario.reduce((acc, k) => acc + (parseInt(d?.entrada?.[k], 10) || 0), 0);
const row = [
formatarDataIsoBr(iso),
unidadeFiltro === 'TODAS' ? 'TODAS' : unidadeFiltro
];
itensUsoDiario.forEach(item => {
row.push(d?.saida?.[item] || 0);
row.push(d?.entrada?.[item] || 0);
});
row.push(totalSaidaDia);
row.push(totalEntradaDia);
row.push(totalSaidaDia + totalEntradaDia);
return row;
});
const head = ['Data', 'Unidade'];
itensUsoDiario.forEach(item => {
head.push(`${item} Saída`);
head.push(`${item} Entrada`);
});
head.push('Total Saída');
head.push('Total Entrada');
head.push('Total Dia');
const unidadeEscopo = unidadeFiltro === 'TODAS' ? 'TODAS AS UNIDADES' : unidadeFiltro;
window.gerarPdfUniversal({
tipo: 'tabela_relatorio',
orientacao: 'landscape',
titulo: 'AUDITORIA DE USO DIARIO - ADMISSAO',
subtitulo: `Periodo: ${formatarDataIsoBr(inicio)} a ${formatarDataIsoBr(fim)} | Unidade foco: ${unidadeEscopo} | Emitido por: ${currentUser.nome} (${currentUser.masp})`,
head,
body,
nomeArquivo: `Auditoria_Uso_Diario_${inicio}_a_${fim}.pdf`
});
};
window.gerarXLSAuditoriaUsoDiario = function() {
itensUsoDiario = window.obterItensUsoDiario();
const inicio = $('hist-uso-inicio')?.value || dataIsoHoje();
const fim = $('hist-uso-fim')?.value || inicio;
const unidadeFiltro = window.obterUnidadeFiltroUsoDiario(true);
if (inicio > fim) {
window.showToast('Data inicial não pode ser maior que a final.', 'error');
return;
}
const info = window.gerarResumoUsoDiario(inicio, fim, unidadeFiltro);
const dias = Object.keys(info.porDia).sort((a, b) => a.localeCompare(b));
if (dias.length === 0) {
alert('Sem consumo de admissão no período selecionado.');
return;
}
const rows = dias.map(iso => {
const d = info.porDia[iso];
const totalSaidaDia = itensUsoDiario.reduce((acc, k) => acc + (parseInt(d?.saida?.[k], 10) || 0), 0);
const totalEntradaDia = itensUsoDiario.reduce((acc, k) => acc + (parseInt(d?.entrada?.[k], 10) || 0), 0);
const row = {
Data: formatarDataIsoBr(iso),
Unidade: unidadeFiltro === 'TODAS' ? 'TODAS' : unidadeFiltro
};
itensUsoDiario.forEach(item => {
row[`${item}_SAIDA`] = d?.saida?.[item] || 0;
row[`${item}_ENTRADA`] = d?.entrada?.[item] || 0;
});
row.TOTAL_SAIDA = totalSaidaDia;
row.TOTAL_ENTRADA = totalEntradaDia;
row.TOTAL_DIA = totalSaidaDia + totalEntradaDia;
return row;
});
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Uso_Diario');
XLSX.writeFile(wb, `Auditoria_Uso_Diario_${inicio}_a_${fim}.xlsx`);
};
window.renderResumoCardsHistoricoUsoDiario = function(info, unidadeFiltro, inicio, fim) {
const modal = $('modal-historico-uso-diario');
if (!modal) return;
const gridLegado = modal.querySelector('#hist-uso-total-TZPR')?.closest('.grid');
if (gridLegado) gridLegado.classList.add('hidden');
const tbody = $('hist-uso-tbody');
const tableWrap = tbody?.closest('.overflow-x-auto');
if (!tableWrap || !tableWrap.parentNode) return;
let host = $('hist-uso-resumo-dinamico');
if (!host) {
host = document.createElement('div');
host.id = 'hist-uso-resumo-dinamico';
host.className = 'mx-5 mb-4 rounded-2xl border border-theme-border bg-gradient-to-r from-theme-body via-black/20 to-theme-body p-4 shadow-lg';
tableWrap.parentNode.insertBefore(host, tableWrap);
}
const cards = itensUsoDiario.map(item => {
const saida = parseInt(info?.totaisPeriodo?.[item], 10) || 0;
const entrada = parseInt(info?.totaisEntrada?.[item], 10) || 0;
const total = saida + entrada;
return `
${item}
S: ${saida}
E: ${entrada}
T: ${total}
`;
}).join('');
host.innerHTML = `
Painel Tático de Movimentação
${formatarDataIsoBr(inicio)} até ${formatarDataIsoBr(fim)} | ${unidadeFiltro === 'TODAS' ? 'TODAS AS UNIDADES' : unidadeFiltro}
`;
};
window.renderHistoricoUsoDiario = function() {
itensUsoDiario = window.obterItensUsoDiario();
const tb = $('hist-uso-tbody');
if (!tb) return;
tb.innerHTML = '';
const inicio = $('hist-uso-inicio')?.value || dataIsoHoje();
const fim = $('hist-uso-fim')?.value || inicio;
const unidadeFiltro = window.obterUnidadeFiltroUsoDiario(true);
if (inicio > fim) {
window.showToast('Data inicial não pode ser maior que a final.', 'error');
return;
}
const table = tb.closest('table');
const headTr = table ? table.querySelector('thead tr') : null;
if (headTr) {
const headCols = ['Data ', 'Unidade ', 'Itens (Saída/Entrada) '];
headCols.push('Origem das Movimentações ');
headTr.innerHTML = headCols.join('');
}
const info = window.gerarResumoUsoDiario(inicio, fim, unidadeFiltro);
window.renderResumoCardsHistoricoUsoDiario(info, unidadeFiltro, inicio, fim);
const selectPainelUnidade = $('uso-diario-unidade');
if (selectPainelUnidade && Array.from(selectPainelUnidade.options || []).some(o => o.value === unidadeFiltro)) {
selectPainelUnidade.value = unidadeFiltro;
}
window.renderResumoUsoDiario();
const dias = Object.keys(info.porDia).sort((a, b) => b.localeCompare(a));
itensUsoDiario.forEach(i => {
const el = $(`hist-uso-total-${i}`);
const s = info.totaisPeriodo[i] || 0;
const e = info.totaisEntrada[i] || 0;
if (el) el.innerHTML = `${s} (+${e} ent) `;
});
if (dias.length === 0) {
const cols = 4;
tb.innerHTML = `Sem consumo no período selecionado. `;
return;
}
const rows = dias.map(iso => {
const d = info.porDia[iso];
const cells = [
`${formatarDataIsoBr(iso)} `,
`${unidadeFiltro === 'TODAS' ? 'TODAS' : unidadeFiltro} `
];
const caixaItens = itensUsoDiario.map(item => {
const qtdSaida = d?.saida?.[item] || 0;
const qtdEntrada = d?.entrada?.[item] || 0;
return `${item} S ${qtdSaida} | E ${qtdEntrada} `;
}).join('');
cells.push(`${caixaItens}
`);
const detalhesMov = Array.isArray(info?.detalhesPorDia?.[iso]) ? info.detalhesPorDia[iso] : [];
const detalheHtml = detalhesMov.length
? detalhesMov.map(det => {
const naturezaSaida = det.natureza === 'saida';
const classeCaixa = naturezaSaida
? 'border-red-500/30 bg-red-500/10'
: 'border-blue-500/30 bg-blue-500/10';
const classeNatureza = naturezaSaida ? 'text-red-300' : 'text-blue-300';
const naturezaTxt = naturezaSaida ? 'SAÍDA' : 'ENTRADA';
const origemEsc = escapeHtml(det.origem || '-');
const destinoEsc = escapeHtml(det.destino || '-');
const fonteEsc = escapeHtml(det.fonte || 'Registro');
const itensResumo = itensUsoDiario
.map(item => {
const qtd = parseInt(det?.itens?.[item], 10) || 0;
return qtd > 0 ? `${item} ${qtd}` : '';
})
.filter(Boolean)
.join(' | ');
return `${naturezaTxt} - ${fonteEsc}
${origemEsc} -> ${destinoEsc}
${escapeHtml(itensResumo || 'Sem itens válidos')}
`;
}).join('')
: 'Sem detalhamento de origem neste dia.
';
cells.push(`${detalheHtml}
`);
return `${cells.join('')} `;
});
tb.innerHTML = rows.join('');
};
window.renderAdmissoes = function() {
window.initAdmissaoOperacaoUI();
const tb = $('uso-tbody'); if(!tb) return; tb.innerHTML = '';
const list = window.obterAdmissoesVisiveis().slice().reverse();
const renderToken = (window._perfCacheAdmissao.renderToken || 0) + 1;
window._perfCacheAdmissao.renderToken = renderToken;
const CHUNK_SIZE = 120;
let idx = 0;
const montarLinha = (a) => {
const idJs = String(a.id || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const dataEsc = escapeHtml(a.data);
const nomeEsc = escapeHtml(a.nome);
const maspEsc = escapeHtml(a.masp);
const setorEsc = escapeHtml(a.setor);
const operacaoRaw = String(a.operacao || 'ENTREGA').toUpperCase();
const isEntrada = operacaoRaw === 'RETORNO';
const operacaoBadge = isEntrada
? 'ENTRADA '
: 'SAÍDA ';
const dI = a.itens
? a.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item)}`).join(', ')
: `${a.qtd}x ${window.formatItemName(a.item)}`;
const itensEsc = escapeHtml(dI);
return `${dataEsc} ${nomeEsc}MASP: ${maspEsc} ${setorEsc} ${operacaoBadge}
${itensEsc} `;
};
const renderBatch = () => {
if (window._perfCacheAdmissao.renderToken !== renderToken) return;
const end = Math.min(idx + CHUNK_SIZE, list.length);
if (end <= idx) return;
const html = [];
for (let i = idx; i < end; i++) html.push(montarLinha(list[i]));
tb.insertAdjacentHTML('beforeend', html.join(''));
idx = end;
if (idx < list.length) requestAnimationFrame(renderBatch);
else if (window.lucide) lucide.createIcons();
};
if (list.length > 0) renderBatch();
window.popularFiltrosUsoDiarioUnidade();
if (window._perfCacheAdmissao.resumoRaf) cancelAnimationFrame(window._perfCacheAdmissao.resumoRaf);
window._perfCacheAdmissao.resumoRaf = requestAnimationFrame(() => window.renderResumoUsoDiario());
}
window.atualizarRotuloBotaoAdmissao = function() {
const op = $('uso-operacao')?.value || 'ENTREGA';
const btn = $('btn-submit-uso');
if (!btn) return;
if (editingAdmissaoId) {
btn.innerHTML = " Atualizar Malote";
} else if (op === 'RETORNO') {
btn.innerHTML = " Confirmar Retorno";
} else {
btn.innerHTML = " Confirmar Saídas";
}
if (window.lucide) lucide.createIcons();
};
window.initAdmissaoOperacaoUI = function() {
const form = $('form-uso');
if (!form || $('uso-operacao-wrap')) {
window.atualizarRotuloBotaoAdmissao();
return;
}
const anchor = form.querySelector('.pt-3');
const wrap = document.createElement('div');
wrap.id = 'uso-operacao-wrap';
wrap.innerHTML = `Tipo de Operação Entrega ao Policial (Saída) Retorno Não Utilizado (Entrada) No retorno, o policial entrega para a Central/Admissão e o sistema gera recibo de devolução.
`;
if (anchor && anchor.parentNode) {
anchor.parentNode.insertBefore(wrap, anchor);
} else {
form.appendChild(wrap);
}
$('uso-operacao')?.addEventListener('change', () => window.atualizarRotuloBotaoAdmissao());
window.atualizarRotuloBotaoAdmissao();
};
window.fatorOperacaoAdmissao = function(op) {
return String(op || 'ENTREGA') === 'RETORNO' ? 1 : -1;
};
window.aplicarMovimentoAdmissaoEstoque = function(setor, itens, fator) {
(itens || []).forEach(it => {
const qtd = parseInt(it.qtd || 0, 10) || 0;
if (!qtd) return;
if (setor === 'Central BH' || setor === 'Admissão/NSI') {
if (window.dbData.inventory?.[it.item]) {
window.dbData.inventory[it.item].atual += (fator * qtd);
}
} else if (window.dbData.nucleosInventory?.[setor]?.[it.item]) {
window.dbData.nucleosInventory[setor][it.item].atual += (fator * qtd);
}
});
};
window.editAdmissao = (id) => {
const a = window.dbData.admissoes.find(x => x.id === id); if(!a) return; editingAdmissaoId = id;
if (!a.seriais) window.usoSeriaisTemporarios = null;
else if (typeof a.seriais === 'string') window.usoSeriaisTemporarios = a.seriais;
else if (Array.isArray(a.seriais)) window.usoSeriaisTemporarios = a.seriais.slice();
else window.usoSeriaisTemporarios = { ...a.seriais };
$('uso-nome').value = a.nome; $('uso-masp').value = a.masp; $('uso-setor').value = a.setor;
if ($('uso-operacao')) $('uso-operacao').value = a.operacao || 'ENTREGA';
if (a.data) {
let dPart = a.data.split(' ')[0];
if (dPart.includes('/')) {
let p = dPart.split('/');
$('uso-data').value = `${p[2]}-${p[1]}-${p[0]}`;
} else {
$('uso-data').value = dPart;
}
}
itensConfig.forEach(i => { const el = $(`uso-qtd-${i}`); const f = a.itens.find(x => x.item === i); if(f) { el.value = f.qtd; el.closest('.uso-item-container').classList.remove('opacity-40'); } else { el.value = '0'; el.closest('.uso-item-container').classList.add('opacity-40'); } });
$('btn-submit-uso').innerHTML = " Atualizar Malote"; $('btn-cancel-edit-uso').classList.remove('hidden'); window.scrollTo({top: 0, behavior: 'smooth'}); window.atualizarRotuloBotaoAdmissao();
};
window.cancelEditAdmissao = () => {
editingAdmissaoId = null; $('form-uso').reset();
window.usoSeriaisTemporarios = null;
if($('uso-data')) { const t = new Date(); $('uso-data').value = `${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,'0')}-${String(t.getDate()).padStart(2,'0')}`; }
if ($('uso-operacao')) $('uso-operacao').value = 'ENTREGA';
itensConfig.forEach(i => { const el = $(`uso-qtd-${i}`); el.value = '0'; el.closest('.uso-item-container').classList.remove('opacity-40'); });
$('btn-submit-uso').innerHTML = " Confirmar Saídas"; $('btn-cancel-edit-uso').classList.add('hidden'); window.atualizarRotuloBotaoAdmissao();
};
window.deleteAdmissao = (id) => {
if(!confirm("Tem a certeza que pretende cancelar a integridade desta rotina Admissão/Uso já cravada na base? O material regressará ao saldo da prateleira virtual.")) return;
const idx = window.dbData.admissoes.findIndex(a => a.id === id); if(idx === -1) return; const a = window.dbData.admissoes[idx];
const opAntiga = a.operacao || 'ENTREGA';
window.aplicarMovimentoAdmissaoEstoque(a.setor, a.itens, -window.fatorOperacaoAdmissao(opAntiga));
window.dbData.admissoes.splice(idx, 1); saveToCloud(['admissoes', 'inventory', 'nucleosInventory']).then(() => { renderAdmissoes(); renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo(); alert("Registo anulado com sucesso do sistema central."); });
};
$('form-uso')?.addEventListener('submit', (e) => {
e.preventDefault(); const s = $('uso-setor').value;
const operacao = $('uso-operacao')?.value || 'ENTREGA';
const fatorOperacao = window.fatorOperacaoAdmissao(operacao);
let dataInput = $('uso-data').value;
let dataFormatada = new Date().toLocaleString('pt-BR');
if (dataInput) {
let p = dataInput.split('-');
let timePart = new Date().toLocaleTimeString('pt-BR');
dataFormatada = `${p[2]}/${p[1]}/${p[0]} ${timePart}`;
}
if(editingAdmissaoId) {
const idx = window.dbData.admissoes.findIndex(x => x.id === editingAdmissaoId); if(idx === -1) return; const old = window.dbData.admissoes[idx];
const oldOperacao = old.operacao || 'ENTREGA';
window.aplicarMovimentoAdmissaoEstoque(old.setor, old.itens, -window.fatorOperacaoAdmissao(oldOperacao));
let hasError = false; let novosItens = [];
itensConfig.forEach(i => { const q = parseInt($(`uso-qtd-${i}`).value, 10); if(q > 0) { let sal = (s === 'Central BH' || s === 'Admissão/NSI') ? window.dbData.inventory[i].atual : (window.dbData.nucleosInventory[s] && window.dbData.nucleosInventory[s][i] ? window.dbData.nucleosInventory[s][i].atual : 0); if(fatorOperacao < 0 && sal < q) hasError = true; novosItens.push({ item: i, qtd: q }); } });
if(hasError || novosItens.length === 0){ window.aplicarMovimentoAdmissaoEstoque(old.setor, old.itens, window.fatorOperacaoAdmissao(oldOperacao)); alert("Operação Negada: Estoque Insuficiente para abater a nova alteração."); return; }
window.aplicarMovimentoAdmissaoEstoque(s, novosItens, fatorOperacao);
let oldTime = old.data.includes(' ') ? old.data.split(' ')[1] : new Date().toLocaleTimeString('pt-BR');
let dataEditada = dataFormatada;
if (dataInput) { let p = dataInput.split('-'); dataEditada = `${p[2]}/${p[1]}/${p[0]} ${oldTime}`; }
window.dbData.admissoes[idx].nome = $val('uso-nome'); window.dbData.admissoes[idx].masp = $val('uso-masp'); window.dbData.admissoes[idx].setor = s; window.dbData.admissoes[idx].itens = novosItens; window.dbData.admissoes[idx].operacao = operacao; window.dbData.admissoes[idx].editadoPor = currentUser.nome;
window.dbData.admissoes[idx].data = dataEditada;
if(window.usoSeriaisTemporarios) { window.dbData.admissoes[idx].seriais = window.usoSeriaisTemporarios; window.usoSeriaisTemporarios = null; }
saveToCloud(['admissoes', 'inventory', 'nucleosInventory']).then(() => { alert("Registro atualizado com sucesso!"); cancelEditAdmissao(); renderAdmissoes(); renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo(); });
} else {
let ok = false; let errorMsg = ""; let itensSelecionados = [];
itensConfig.forEach(i => { const q = parseInt($(`uso-qtd-${i}`).value, 10); if(q > 0) { let sal = (s === 'Central BH' || s === 'Admissão/NSI') ? window.dbData.inventory[i].atual : (window.dbData.nucleosInventory[s] && window.dbData.nucleosInventory[s][i] ? window.dbData.nucleosInventory[s][i].atual : 0); if(fatorOperacao < 0 && sal < q) { errorMsg += `Aviso: Estoque de [ ${i} ] não apresenta saldo suficiente. `; } itensSelecionados.push({ item: i, qtd: q }); ok = true; } });
if(!ok) { alert("Informe a quantidade de pelo menos um equipamento."); return; } if(errorMsg) { alert("Transação Congelada: \n" + errorMsg); return; }
window.aplicarMovimentoAdmissaoEstoque(s, itensSelecionados, fatorOperacao);
window.dbData.admissoes.push({ id: "USO-" + Date.now().toString().slice(-4) + Math.floor(Math.random()*1000), data: dataFormatada, nome: $val('uso-nome'), masp: $val('uso-masp'), setor: s, operacao: operacao, itens: itensSelecionados, registradoPor: currentUser.nome, seriais: window.usoSeriaisTemporarios || {} });
window.usoSeriaisTemporarios = null;
saveToCloud(['admissoes', 'inventory', 'nucleosInventory']).then(() => { const msgSucesso = operacao === 'RETORNO' ? "Sucesso! Retorno não utilizado recebido e registrado com recibo de devolução disponível." : "Sucesso! O armamento tecnológico foi faturado positivamente. O Registo está fixado na Cloud do Estado."; alert(msgSucesso); e.target.reset(); if($('uso-data')) { const t = new Date(); $('uso-data').value = `${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,'0')}-${String(t.getDate()).padStart(2,'0')}`; } if ($('uso-operacao')) $('uso-operacao').value = 'ENTREGA'; itensConfig.forEach(i => $(`uso-qtd-${i}`).value = '0'); window.atualizarRotuloBotaoAdmissao(); renderAdmissoes(); renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo(); });
}
});
window.abrirModalPerfil = () => { const user = window.dbData.users.find(u => u.masp === currentUser.masp); if(user) { $('perfil-info-nome').innerText = `${user.nome} - MASP: ${user.masp}`; } $('modal-perfil').classList.replace('hidden', 'flex'); };
window.fecharModalPerfil = () => { $('modal-perfil').classList.replace('flex', 'hidden'); };
$('form-perfil')?.addEventListener('submit', (e) => { e.preventDefault(); const novaSenha = $val('perfil-nova-senha'); if (!novaSenha) return; const idx = window.dbData.users.findIndex(u => u.masp === currentUser.masp); if (idx > -1) { window.dbData.users[idx].senha = novaSenha; saveToCloud(['users']).then(() => { alert("Senha atualizada com sucesso!"); fecharModalPerfil(); }); } });
$('form-outros-estados')?.addEventListener('submit', (e) => { e.preventDefault(); if (!window.dbData.outrosEstados) window.dbData.outrosEstados = []; window.dbData.outrosEstados.push({ id: "OE-" + Date.now().toString().slice(-4) + Math.floor(Math.random() * 100), data: new Date().toLocaleString('pt-BR'), serial: $val('oe-serial'), estado: $val('oe-estado'), orgao: $val('oe-orgao'), endereco: $val('oe-endereco'), registradoPor: currentUser.nome }); saveToCloud(['outrosEstados']).then(() => { alert("Equipamento externo registrado e acautelado com sucesso!"); e.target.reset(); renderOutrosEstados(); }); });
window.gerarPDFOutrosEstados = () => { let list = window.dbData.outrosEstados || []; if(list.length === 0) { alert("Nenhum equipamento de outro Estado registrado."); return; } let bD = list.map(oe => [oe.data.split(' ')[0], oe.serial, oe.estado, oe.orgao, oe.endereco]); window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'landscape', titulo: "RELATÓRIO DE EQUIPAMENTOS DE OUTROS ESTADOS", subtitulo: `Gerado em: ${new Date().toLocaleString('pt-BR')} por ${currentUser.nome}`, head: ['Data', 'Serial / ID', 'Estado', 'Órgão', 'Endereço'], body: bD, nomeArquivo: `Relatorio_OutrosEstados_${new Date().getTime()}.pdf` }); };
window.renderOutrosEstados = () => {
const tb = $('oe-tbody'); if(!tb) return; tb.innerHTML = '';
let list = window.dbData.outrosEstados || [];
if(list.length === 0) {
tb.innerHTML = `Nenhum equipamento externo acautelado. `;
return;
}
list.slice().reverse().forEach(oe => {
tb.innerHTML += `${oe.data} ${oe.serial} ${oe.estado} ${oe.orgao}${oe.endereco} `;
});
lucide.createIcons();
};
window.apagarOutrosEstados = (id) => {
if(currentUser.role !== 'admin' && currentUser.role !== 'central') { alert("Acesso Negado: Apenas a gestão central pode remover registros."); return; }
if(confirm("ATENÇÃO: Deseja apagar este acautelamento definitivamente?")) {
window.dbData.outrosEstados = window.dbData.outrosEstados.filter(oe => oe.id !== id);
saveToCloud(['outrosEstados']).then(() => { renderOutrosEstados(); });
}
};
window.prepararReciboUso = () => {
const nm = $val('uso-nome'), ma = $val('uso-masp'), set = $('uso-setor').value;
const op = $('uso-operacao')?.value || 'ENTREGA';
if(!nm || !ma) { alert("Preencha as caixas NOME e MASP DO POLICIAL no formulário ao lado antes de gerar o recibo oficial."); return; }
let iS = []; itensConfig.forEach(i => { const q = parseInt($(`uso-qtd-${i}`).value, 10); if(q > 0) iS.push({ item: i, qtd: q }); });
if(iS.length === 0) { alert("Registe a volumetria de pelo menos um equipamento."); return; }
reciboPendenteDados = { nome: nm, masp: ma, setor: set, operacao: op, itens: iS };
let nS = false; const c = $('recibo-seriais-container'); c.innerHTML = '';
const registroEditando = editingAdmissaoId ? (window.dbData.admissoes || []).find(x => x.id === editingAdmissaoId) : null;
const fonteSeriais = window.usoSeriaisTemporarios ?? registroEditando?.seriais ?? null;
const obterSerialItem = (fonte, item) => {
if (!fonte) return '';
if (typeof fonte === 'string') return fonte.trim();
if (Array.isArray(fonte)) return fonte.join('\n').trim();
const porChave = fonte[item] ?? fonte[String(item).toUpperCase()] ?? fonte[String(item).toLowerCase()];
if (Array.isArray(porChave)) return porChave.join('\n').trim();
if (typeof porChave === 'string') return porChave.trim();
const chaveEncontrada = Object.keys(fonte).find(k => String(k).toUpperCase() === String(item).toUpperCase());
if (chaveEncontrada) {
const v = fonte[chaveEncontrada];
if (Array.isArray(v)) return v.join('\n').trim();
if (typeof v === 'string') return v.trim();
}
return '';
};
iS.forEach(it => { if (it.item !== 'CINTAS' && it.item !== 'FONTE' && !it.item.startsWith('MANUAL')) { nS = true; let isT = it.item === 'TRAVAS'; let inst = isT ? "Faixa de Seriais" : "Seriais"; let pl = isT ? "Ex: TXH... - TXH..." : "Bipar Número de Série Físico Identificado."; const savedSerial = obterSerialItem(fonteSeriais, it.item); c.innerHTML += `${inst}: [ ${it.qtd}x ${window.formatItemName(it.item)} ] 0
`; } });
if(!nS) c.innerHTML = 'Trânsito Flexível: Os equipamentos deste lote dispensam registo individual.
'; openModal('modal-recibo'); window.anexarGeradorRapidoTravasNoRecibo();
};
window.prepararReciboUsoHist = (id) => {
const a = window.dbData.admissoes.find(x => x.id === id); if(!a) return;
reciboPendenteDados = { id: a.id, nome: a.nome, masp: a.masp, setor: a.setor, operacao: a.operacao || 'ENTREGA', itens: a.itens };
const container = $('recibo-seriais-container'); container.innerHTML = ''; let needsSerials = false;
const obterSeriaisItem = (registro, item) => {
const fonteSeriais = registro?.seriais;
if (!fonteSeriais) return '';
if (typeof fonteSeriais === 'string') return fonteSeriais.trim();
if (Array.isArray(fonteSeriais)) return fonteSeriais.join('\n').trim();
const valorItem = fonteSeriais[item];
if (Array.isArray(valorItem)) return valorItem.join('\n').trim();
if (typeof valorItem === 'string') return valorItem.trim();
const serialsBipados = registro?.serialsBipados;
if (typeof serialsBipados === 'string') return serialsBipados.trim();
return '';
};
a.itens.forEach(it => {
if (it.item !== 'CINTAS' && it.item !== 'FONTE' && !it.item.startsWith('MANUAL')) {
needsSerials = true; let isTravas = it.item === 'TRAVAS'; let instrucao = isTravas ? "Faixa de Seriais" : "Seriais/Obs"; let placeholder = isTravas ? "Ex: TXHXXXXXX - TXHXXXXXX" : "Bipar Número de Série do Equipamento.";
let savedSerial = obterSeriaisItem(a, it.item);
container.innerHTML += `${instrucao}: [ ${it.qtd} x ${window.formatItemName(it.item)} ] 0
`;
setTimeout(() => window.atualizarContagemSeriais(`serial-recibo-${it.item}`), 50);
}
});
if(!needsSerials) { container.innerHTML = 'Nota Interna: Estes equipamentos dispensam o controlo de rastreabilidade pontual (sem seriais).
'; }
openModal('modal-recibo');
window.anexarGeradorRapidoTravasNoRecibo();
};
window.confirmarSeriaisRecibo = () => {
if (!reciboPendenteDados) { alert("Base Analítica Vazia. Sem Dados Vitais Recuperados."); return; }
let seriaisDoRecibo = {};
reciboPendenteDados.itens.forEach(it => {
if(it.item !== 'CINTAS' && it.item !== 'FONTE' && !it.item.startsWith('MANUAL')) {
const e = $(`serial-recibo-${it.item}`);
if(e) seriaisDoRecibo[it.item] = e.value.trim();
}
});
window.usoSeriaisTemporarios = seriaisDoRecibo;
if(reciboPendenteDados.id) {
const idx = window.dbData.admissoes.findIndex(a => a.id === reciboPendenteDados.id);
if(idx > -1) {
window.dbData.admissoes[idx].seriais = seriaisDoRecibo;
saveToCloud(['admissoes']).then(() => {
window.showToast("Seriais registrados no histórico com sucesso!", "success");
renderAdmissoes();
});
}
} else {
window.showToast("Seriais vinculados temporariamente. Finalize o formulário de Admissão.", "success");
}
closeModal('modal-recibo');
};
window.fecharModalRecibo = () => { $('modal-recibo').classList.replace('flex', 'hidden'); reciboPendenteDados = null; };
window.gerarPDF = async () => {
try {
if (!reciboPendenteDados) { alert("Base Analítica Vazia. Sem Dados Vitais Recuperados."); return; }
let nU = reciboPendenteDados.setor || "Unidade Base PPMG Estadual", cid = "Belo Horizonte";
const opRecibo = reciboPendenteDados.operacao || 'ENTREGA';
if (nU !== "Central BH" && nU !== "Admissão/NSI") cid = nU.replace("Núcleo ", "").replace("Subnúcleo ", "");
const mp = { 'TZPR': 'TZPRs', 'CM': 'CMs', 'CINTAS': 'Cintas', 'TRAVAS': 'Travas', 'FONTE': 'Fontes Carregadoras', 'UPR': 'UPRs', 'MANUAL_MONIT': 'Manuais do Monitorado', 'MANUAL_VITIMA': 'Manuais da Vítima', 'MANUAL_APP': 'Manuais APP' };
let qA = []; (reciboPendenteDados.itens || []).forEach(it => { let nomeFormatado = mp[it.item] || window.formatItemName(it.item); if(it.item !== 'CINTAS' && it.item !== 'FONTE' && !String(it.item).startsWith('MANUAL')) qA.push(`${it.qtd} ${nomeFormatado} (seriais abaixo)`); else qA.push(`${it.qtd} ${nomeFormatado}`); });
let tL = qA.length > 1 ? qA.join(", e ").replace(/,([^,]*)$/, " e$1") : (qA[0] || "");
let seriaisDoRecibo = {}; reciboPendenteDados.itens.forEach(it => { if(it.item !== 'CINTAS' && it.item !== 'FONTE' && !it.item.startsWith('MANUAL')) { const e = $(`serial-recibo-${it.item}`); if(e) seriaisDoRecibo[it.item] = e.value.trim(); } });
window.usoSeriaisTemporarios = seriaisDoRecibo;
if(reciboPendenteDados.id) {
const idx = window.dbData.admissoes.findIndex(a => a.id === reciboPendenteDados.id);
if(idx > -1) {
window.dbData.admissoes[idx].seriais = seriaisDoRecibo;
saveToCloud(['admissoes']);
}
}
const myLotacao = currentUser.role === 'nucleo' ? (currentUser.unidades||[])[0] : 'PPMG-CD/DME';
const obsPergunta = opRecibo === 'RETORNO'
? "Deseja inserir alguma observação neste recibo de devolução? (Deixe em branco se não)"
: "Deseja inserir alguma observação neste recibo de uso? (Deixe em branco se não)";
const nomeArquivo = opRecibo === 'RETORNO'
? `Recibo_Devolucao_Equipamentos_${reciboPendenteDados.masp}.pdf`
: `Recibo_SIGME_Uso_${reciboPendenteDados.masp}.pdf`;
const destinoPdf = opRecibo === 'RETORNO'
? 'Admissão/NSI'
: (nU === "Central BH" ? "Admissão/NSI" : nU);
const nomeRecebedor = opRecibo === 'RETORNO' ? currentUser.nome : reciboPendenteDados.nome;
const maspRecebedor = opRecibo === 'RETORNO' ? currentUser.masp : reciboPendenteDados.masp;
const lotacaoRecebedor = opRecibo === 'RETORNO' ? 'PPMG-CD/DME' : ((nU !== "Central BH" && nU !== "Admissão/NSI") ? nU : "PPMG-CD/DME");
const nomeRepassador = opRecibo === 'RETORNO' ? reciboPendenteDados.nome : currentUser.nome;
const maspRepassador = opRecibo === 'RETORNO' ? reciboPendenteDados.masp : currentUser.masp;
const lotacaoRepassador = opRecibo === 'RETORNO' ? ((nU !== "Central BH" && nU !== "Admissão/NSI") ? nU : 'UNIDADE EXTERNA') : myLotacao;
const obs = prompt(obsPergunta);
await window.gerarPdfUniversal({
tipoLayout: 'USO', destino: destinoPdf, textoLista: tL, itens: reciboPendenteDados.itens, seriaisObj: seriaisDoRecibo, observacoes: obs,
nomeRecebedor, maspRecebedor, lotacaoRecebedor,
nomeRepassador, maspRepassador, lotacaoRepassador,
cidade: cid, nomeArquivo
});
closeModal('modal-recibo');
} catch (error) { console.error(error); alert("Falha ao montar o documento PDF."); }
};
window.atualizarBotaoRefazerTriagem = () => {
const btnEnvio = $('btn-refazer-cancelamento-triagem-envio');
const btn = $('btn-refazer-cancelamento-triagem');
if (!btn && !btnEnvio) return;
const usuarioTriagem = window.currentUser || currentUser;
const isGestor = ['admin', 'central'].includes(String(usuarioTriagem?.role || ''));
const ultimo = window.ultimoCancelamentoReciboSpacecom;
const ultimoEnvio = window.ultimoCancelamentoEnvioTriagem || window.obterUltimoCancelamentoEnvioTriagem?.();
if (!window.ultimoCancelamentoEnvioTriagem && ultimoEnvio) {
window.ultimoCancelamentoEnvioTriagem = ultimoEnvio;
}
if (btnEnvio) {
if (isGestor && ultimoEnvio && Array.isArray(ultimoEnvio.itens) && ultimoEnvio.itens.length > 0) {
btnEnvio.classList.remove('hidden');
btnEnvio.innerHTML = ` Refazer Cancelamento #${ultimoEnvio.reciboNum}`;
} else {
btnEnvio.classList.add('hidden');
}
}
if (isGestor && ultimo && Array.isArray(ultimo.itens) && ultimo.itens.length > 0) {
if (btn) {
btn.classList.remove('hidden');
btn.innerHTML = ` Refazer Cancelamento #${ultimo.reciboId}`;
}
} else {
if (btn) btn.classList.add('hidden');
}
};
window.abrirModalHistoricoTriagem = () => {
const container = document.getElementById('lista-historico-triagem');
if (!container) { openModal('modal-historico-triagem'); return; }
container.innerHTML = '';
const usuarioTriagem = window.currentUser || currentUser;
const isGestor = ['admin', 'central'].includes(String(usuarioTriagem?.role || ''));
let list = (window.dbData.movimentosManuais || []).filter(m => m.tipo === 'Saída para Triagem/Manutenção');
if (['nucleo', 'spacecom'].includes(usuarioTriagem?.role)) {
const unidadesLower = (usuarioTriagem.unidades || []).map(u => (u || '').trim().toLowerCase());
list = list.filter(m => {
const unidadeMaiorLower = (m.unidade || '').trim().toLowerCase();
return unidadesLower.some(u =>
(m.obs || '').toLowerCase().includes(`unidade: ${u}`) ||
u === unidadeMaiorLower ||
m.responsavel === usuarioTriagem.nome
);
});
}
list = list.slice().reverse();
if (list.length === 0) {
container.innerHTML = 'Nenhum envio registrado.
';
} else {
const recibosComRefazerRenderizados = new Set();
list.forEach(t => {
// Buscar info do colaborador Spacecom que recebeu
const recebimento = (window.dbData.movimentosManuais || []).find(m =>
m.tipo === 'Recebimento Spacecom' && (
String(m.reciboOriginal || '').trim() === String(t.recibo || t.id || '').trim() ||
String(m.obs || '').includes(`Original: ${t.recibo || t.id}`)
)
);
const aceite = t.aceiteSpacecom || (recebimento ? { nome: recebimento.responsavel, cpf: recebimento.cpf, data: recebimento.data } : null);
const statusBadge = t.status === 'Cancelado'
? '⛔ Cancelado '
: t.status === 'Em Inspeção Spacecom' || t.status === 'Em Triagem'
? 'Em Inspeção '
: t.status === 'Devolvido ao Policial' || t.status === 'Recebido pela Unidade'
? 'Concluído '
: t.status === 'Pendente'
? 'Aguardando Spacecom '
: `${t.status || 'N/A'} `;
const spacecomInfo = aceite
? `
✅ Recebido pela Spacecom
${aceite.nome || 'N/A'}
CPF: ${aceite.cpf ? String(aceite.cpf).replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4') : 'N/A'}
${aceite.data || ''}
`
: `⏳ Aguardando conferência Spacecom
`;
const podeEditar = ['nucleo', 'admin', 'central'].includes(String(usuarioTriagem?.role || '')) || t.responsavel === usuarioTriagem?.nome;
const reciboId = t.recibo || t.id;
const envioToken = String(t.id || t.recibo || '').trim();
const envioIdSeguro = encodeURIComponent(envioToken);
const seriaisTitulo = String(t.seriais || '').replace(/"/g, '"');
const btnEditar = podeEditar && t.status === 'Pendente'
? ` Editar `
: '';
const cancelarToken = String(reciboId || t.id || '').trim();
const cancelarTokenSeguro = encodeURIComponent(cancelarToken);
const btnCancelar = isGestor && t.status !== 'Devolvido ao Policial' && t.status !== 'Recebido pela Unidade' && t.status !== 'Cancelado'
? ` Cancelar `
: '';
const chaveRecibo = window.normalizarChaveReciboTriagem(reciboId);
const mostrarRefazerItem = isGestor && t.status === 'Cancelado' && !recibosComRefazerRenderizados.has(chaveRecibo);
if (mostrarRefazerItem) recibosComRefazerRenderizados.add(chaveRecibo);
const reciboRefazerSeguro = encodeURIComponent(String(reciboId || ''));
const btnRefazerItem = mostrarRefazerItem
? ` Refazer Este Cancelamento `
: '';
const btnPdf = ` PDF `;
container.innerHTML += `
#${reciboId}
${statusBadge}
${t.data || ''}
Expedidor: ${t.responsavel || 'N/A'}
Unidade: ${t.unidade || 'N/A'} | Motivo: ${t.motivo || 'N/A'}
${t.qtd || 0}x ${window.formatItemName(t.item)}
${t.seriais ? `
Seriais: ${String(t.seriais).substring(0, 80)}${String(t.seriais).length > 80 ? '…' : ''}
` : ''}
${t.editadoPor ? `
Editado por ${t.editadoPor} em ${t.editadoEm}
` : ''}
${spacecomInfo}
${btnPdf}${btnEditar}${btnCancelar}${btnRefazerItem}
`;
});
}
window.atualizarBotaoRefazerTriagem();
openModal('modal-historico-triagem');
lucide.createIcons();
};
window.abrirModalHistoricoEntrada = () => {
const container = document.getElementById('lista-historico-entrada');
if (!container) { openModal('modal-historico-entrada'); return; }
container.innerHTML = '';
const usuarioEntrada = window.currentUser || currentUser;
const isGestor = ['admin', 'central'].includes(String(usuarioEntrada?.role || ''));
let list = (window.dbData.movimentosManuais || []).filter(m =>
m.tipo === 'Entrada de Novos (Enviado pela Central)' ||
m.tipo === 'Retorno de Manutenção (Limpos)' ||
m.tipo === 'Entrada de Novos (Fornecedor)'
);
if (['nucleo'].includes(usuarioEntrada?.role)) {
const unidadesLower = (usuarioEntrada.unidades || []).map(u => (u || '').trim().toLowerCase());
list = list.filter(m => {
const unidadeMaiorLower = (m.unidade || '').trim().toLowerCase();
return unidadesLower.some(u =>
u === unidadeMaiorLower ||
(m.obs || '').toLowerCase().includes(u) ||
m.responsavel === usuarioEntrada.nome
);
});
}
list = list.slice().reverse();
if (list.length === 0) {
container.innerHTML = 'Nenhum recebimento registrado.
';
} else {
list.forEach(t => {
const statusBadge = `Registrado `;
const podeEditar = usuarioEntrada.role === 'nucleo' || usuarioEntrada.role === 'admin' || usuarioEntrada.role === 'central' || t.responsavel === usuarioEntrada.nome;
const btnEditar = podeEditar
? ` Editar `
: '';
const btnApagar = isGestor
? ` Apagar `
: '';
container.innerHTML += `
#${t.id}
${statusBadge}
${t.data || ''}
Responsável: ${t.responsavel || 'N/A'}
Unidade Receptora: ${t.unidade || t.destino || (String(t.obs||'').match(/Destino:\s*([^.]+)/)?.[1]||'N/A').trim()}
Origem: ${t.origemConferencia || t.tipo || 'N/A'}
${t.qtd || 0}x ${window.formatItemName(t.item)}
${t.seriais ? `
Seriais: ${String(t.seriais).substring(0, 80)}${String(t.seriais).length > 80 ? '…' : ''}
` : ''}
${t.obs ? `
Obs: ${String(t.obs).substring(0, 120)}
` : ''}
${t.editadoPor ? `
Editado por ${t.editadoPor} em ${t.editadoEm}
` : ''}
${btnEditar}${btnApagar}
`;
});
}
const btnRefazer = $('btn-refazer-apagar-entrada');
const isGestorBtn = ['admin', 'central'].includes(String(usuarioEntrada?.role || ''));
if (btnRefazer) {
const undo = window.ultimoApagadoEntrada;
if (isGestorBtn && undo && undo.registro) {
btnRefazer.classList.remove('hidden');
btnRefazer.innerHTML = ` Refazer Exclusão #${undo.registro.id}`;
} else {
btnRefazer.classList.add('hidden');
}
}
openModal('modal-historico-entrada');
lucide.createIcons();
};
window.abrirEdicaoEnvioTriagem = function(id) {
const t = (window.dbData.movimentosManuais || []).find(m => m.id === id);
if (!t) { window.showToast('Registro não encontrado.', 'error'); return; }
// Reutiliza o modal-editar-entrada com contexto de triagem
document.getElementById('edit-entrada-id').value = id;
document.getElementById('edit-entrada-origem').value = t.obs ? (t.obs.split('Origem Fato:')[1] || t.origemConferencia || t.unidade || '') : '';
document.getElementById('edit-entrada-destino').value = t.unidade || '';
document.getElementById('edit-entrada-obs').value = t.obs || '';
document.getElementById('edit-entrada-seriais').value = t.seriais || t.serialsBipados || '';
document.getElementById('edit-entrada-id').dataset.contexto = 'triagem';
openModal('modal-editar-entrada');
lucide.createIcons();
};
window.abrirEdicaoEntrada = function(id) {
const t = (window.dbData.movimentosManuais || []).find(m => m.id === id);
if (!t) { window.showToast('Registro não encontrado.', 'error'); return; }
document.getElementById('edit-entrada-id').value = id;
document.getElementById('edit-entrada-origem').value = t.origemConferencia || t.tipo || '';
document.getElementById('edit-entrada-destino').value = t.unidade || t.destino || '';
document.getElementById('edit-entrada-obs').value = t.obs || '';
document.getElementById('edit-entrada-seriais').value = t.seriais || t.serialsBipados || '';
document.getElementById('edit-entrada-id').dataset.contexto = 'entrada';
openModal('modal-editar-entrada');
lucide.createIcons();
};
window.salvarEdicaoEntrada = function() {
const id = document.getElementById('edit-entrada-id')?.value;
const contexto = document.getElementById('edit-entrada-id')?.dataset?.contexto || 'entrada';
if (!id) return;
const t = (window.dbData.movimentosManuais || []).find(m => m.id === id);
if (!t) { window.showToast('Registro não encontrado.', 'error'); return; }
const user = window.currentUser || currentUser;
t.origemConferencia = (document.getElementById('edit-entrada-origem')?.value || '').trim();
t.unidade = (document.getElementById('edit-entrada-destino')?.value || '').trim();
t.obs = (document.getElementById('edit-entrada-obs')?.value || '').trim();
const seriais = (document.getElementById('edit-entrada-seriais')?.value || '').trim();
t.seriais = seriais;
t.serialsBipados = seriais;
t.editadoPor = user?.nome || 'USUÁRIO';
t.editadoEm = new Date().toLocaleString('pt-BR');
saveToCloud(['movimentosManuais']).then(() => {
closeModal('modal-editar-entrada');
if (contexto === 'triagem') { window.abrirModalHistoricoTriagem(); }
else { window.abrirModalHistoricoEntrada(); }
window.showToast('Registro atualizado com sucesso.', 'success');
});
};
window.apagarEntrada = function(id) {
const user = window.currentUser || currentUser;
if (!['admin', 'central'].includes(String(user?.role || ''))) {
window.showToast('Apenas o gestor pode apagar registros.', 'error');
return;
}
if (!confirm('Apagar este registro de recebimento? Esta ação não pode ser desfeita.')) return;
const idx = (window.dbData.movimentosManuais || []).findIndex(m => m.id === id);
if (idx === -1) { window.showToast('Registro não encontrado.', 'error'); return; }
window.ultimoApagadoEntrada = { idx, registro: { ...window.dbData.movimentosManuais[idx] } };
window.dbData.movimentosManuais.splice(idx, 1);
saveToCloud(['movimentosManuais']).then(() => {
window.abrirModalHistoricoEntrada();
window.showToast('Registro removido com sucesso.', 'success');
});
};
window.refazerApagarEntrada = function() {
const user = window.currentUser || currentUser;
if (!['admin', 'central'].includes(String(user?.role || ''))) {
window.showToast('Apenas o gestor pode refazer exclusões.', 'error');
return;
}
const undo = window.ultimoApagadoEntrada;
if (!undo || !undo.registro) {
window.showToast('Não há exclusão recente para refazer.', 'error');
return;
}
if (!confirm(`Refazer exclusão do registro #${undo.registro.id}?`)) return;
window.dbData.movimentosManuais.push(undo.registro);
saveToCloud(['movimentosManuais']).then(() => {
window.ultimoApagadoEntrada = null;
window.abrirModalHistoricoEntrada();
window.showToast('Exclusão desfeita com sucesso.', 'success');
}).catch(() => window.showToast('Erro ao refazer exclusão.', 'error'));
};
window.abrirModalHistoricoDevolucoes = () => {
const tb = $('tbody-historico-devolucoes'); tb.innerHTML = '';
let list = (window.obterListaTransferenciasMescladaHistoricoLocal ? window.obterListaTransferenciasMescladaHistoricoLocal() : (window.dbData.transferencias || []))
.filter(m => m.destino === 'Central BH')
.slice()
.reverse();
if(currentUser.role === 'nucleo') {
const unidadesLower = (currentUser.unidades||[]).map(u => (u || '').trim().toLowerCase());
list = list.filter(t => {
const origemLower = (t.origem || '').trim().toLowerCase();
const destinoLower = (t.destino || '').trim().toLowerCase();
return unidadesLower.includes(origemLower) || unidadesLower.includes(destinoLower);
});
}
if(list.length === 0) { tb.innerHTML = `Não constam registros prévios. `; }
else { list.forEach(t => { let descItens = t.itens ? t.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item)}`).join(', ') : `${t.qtd}x ${window.formatItemName(t.item)}`; let srs = ""; if(t.seriais) { Object.keys(t.seriais).forEach(k => { srs += `${k}: ${t.seriais[k]} `; }); } tb.innerHTML += `${t.data} ${t.origem} ${descItens} ${srs || '-'} ${t.status} `; }); }
openModal('modal-historico-devolucoes');
};
window.abrirModalHistoricoAuditoria = () => {
const tb = $('tbody-historico-auditoria'); tb.innerHTML = ''; let list = (window.dbData.auditorias || []).slice().reverse();
if(list.length === 0) { tb.innerHTML = `Nenhum histórico. `; }
else { list.forEach(a => { let srs = a.seriais.join(', '); tb.innerHTML += `${a.data} ${a.responsavel}MASP PPMG: ${a.masp} ${a.item || 'TZPR'} ${a.origem || 'NÃO INFORMADA'} ${a.qtd} ${srs} `; }); }
openModal('modal-historico-auditoria');
};
window.fecharModalHistoricoTriagem = () => closeModal('modal-historico-triagem');
window.fecharModalHistoricoDevolucoes = () => closeModal('modal-historico-devolucoes');
window.fecharModalHistoricoAuditoria = () => closeModal('modal-historico-auditoria');
window.abrirModalHistoricoRecibosSpacecom = () => {
const container = document.getElementById('lista-historico-recibos-spacecom');
if (!container) { openModal('modal-historico-recibos-spacecom'); return; }
container.innerHTML = '';
const user = window.currentUser || currentUser;
const isGestor = ['admin', 'central'].includes(String(user?.role || ''));
const lista = (window.dbData.movimentosManuais || []).filter(m => m.tipo === 'Saída Spacecom para Unidade').slice().reverse();
const grupos = {};
lista.forEach(m => {
const key = m.recibo || m.id;
if (!grupos[key]) {
grupos[key] = {
id: key,
data: m.data || '-',
destino: m.unidadeDestino || m.unidade || 'N/A',
origem: m.origemConferencia || 'NÃO INFORMADA',
status: m.status || 'Pendente',
itens: [],
seriais: []
};
}
grupos[key].itens.push(`${m.qtd}x ${window.formatItemName(m.item)}`);
const seriaisRegistro = String(m.seriais || m.serialsBipados || '').trim();
if (seriaisRegistro) {
grupos[key].seriais.push(`${window.formatItemName(m.item)}: ${seriaisRegistro}`);
}
});
const agrupados = Object.values(grupos);
if (agrupados.length === 0) {
container.innerHTML = 'Nenhum recibo gerado pela Spacecom.
';
} else {
agrupados.forEach(r => {
const seriaisPreview = r.seriais.length > 0
? r.seriais.map(s => String(s).replace(/\n/g, ', ')).join(' | ')
: '-';
const statusBadge = `${r.status} `;
const btnPdf = ` PDF `;
const btnEditar = ` Editar `;
const btnCancelar = isGestor
? ` Cancelar `
: '';
container.innerHTML += `
#${r.id}
${statusBadge}
${r.data}
Destino: ${r.destino}
Origem Material: ${r.origem}
${r.itens.join(' | ')}
Seriais: ${seriaisPreview.substring(0, 120)}${seriaisPreview.length > 120 ? '…' : ''}
${btnPdf}${btnEditar}${btnCancelar}
`;
});
}
openModal('modal-historico-recibos-spacecom');
lucide.createIcons();
};
window.cancelarReciboSpacecomSaida = function(reciboId) {
const user = window.currentUser || currentUser;
if (!['admin', 'central'].includes(String(user?.role || ''))) {
window.showToast('Apenas o gestor pode cancelar recibos Spacecom.', 'error');
return;
}
if (!confirm(`Cancelar o recibo #${reciboId} (Saída Spacecom)?\n\nOs itens serão estornados ao estoque Central BH.`)) return;
let totalEstornado = 0;
const snapshot = { reciboId, dataCancelamento: new Date().toLocaleString('pt-BR'), itens: [] };
(window.dbData.movimentosManuais || []).forEach(m => {
if (m.tipo === 'Saída Spacecom para Unidade' && (m.recibo || m.id) === reciboId) {
const itemKey = String(m.item || '').toUpperCase();
const qtd = parseInt(m.qtd || 0, 10);
if (window.dbData.inventory && window.dbData.inventory[itemKey]) {
window.dbData.inventory[itemKey].atual += qtd;
totalEstornado += qtd;
}
snapshot.itens.push({
id: m.id,
itemKey,
qtd,
statusAnterior: m.status || 'Pendente',
canceladoPorAnterior: m.canceladoPor || null,
canceladoEmAnterior: m.canceladoEm || null
});
m.status = 'Cancelado';
m.canceladoPor = user.nome || 'GESTOR';
m.canceladoEm = new Date().toLocaleString('pt-BR');
}
});
if (snapshot.itens.length === 0) {
window.showToast(`Nenhum registro localizado para o recibo #${reciboId}.`, 'error');
return;
}
window.ultimoCancelamentoReciboSpacecom = snapshot;
window.limparReferenciasCanceladas?.();
saveToCloud(['movimentosManuais', 'inventory']).then(() => {
window.abrirModalHistoricoRecibosSpacecom();
if ($('modal-historico-triagem')?.classList.contains('flex')) {
window.atualizarBotaoRefazerTriagem();
lucide.createIcons();
}
window.showToast(`Recibo #${reciboId} cancelado. ${totalEstornado} item(s) devolvido(s) ao estoque.`, 'success');
});
};
window.refazerCancelamentoReciboSpacecom = function(contexto = 'triagem') {
const user = window.currentUser || currentUser;
if (!['admin', 'central'].includes(String(user?.role || ''))) {
window.showToast('Apenas o gestor pode refazer cancelamentos.', 'error');
return;
}
const ultimo = window.ultimoCancelamentoReciboSpacecom;
if (!ultimo || !Array.isArray(ultimo.itens) || ultimo.itens.length === 0) {
window.showToast('Não há cancelamento recente para refazer.', 'error');
return;
}
if (!confirm(`Refazer o cancelamento do recibo #${ultimo.reciboId}?\n\nOs saldos serão restaurados para o estado anterior.`)) return;
let totalRevertido = 0;
ultimo.itens.forEach(it => {
const mov = (window.dbData.movimentosManuais || []).find(m => m.id === it.id);
if (!mov) return;
if (window.dbData.inventory && window.dbData.inventory[it.itemKey]) {
window.dbData.inventory[it.itemKey].atual -= parseInt(it.qtd || 0, 10);
totalRevertido += parseInt(it.qtd || 0, 10);
}
mov.status = it.statusAnterior || 'Pendente';
if (it.canceladoPorAnterior) mov.canceladoPor = it.canceladoPorAnterior; else delete mov.canceladoPor;
if (it.canceladoEmAnterior) mov.canceladoEm = it.canceladoEmAnterior; else delete mov.canceladoEm;
mov.refeitoPor = user.nome || 'GESTOR';
mov.refeitoEm = new Date().toLocaleString('pt-BR');
});
saveToCloud(['movimentosManuais', 'inventory']).then(() => {
window.ultimoCancelamentoReciboSpacecom = null;
if (contexto === 'triagem') window.abrirModalHistoricoTriagem(); else window.abrirModalHistoricoRecibosSpacecom();
window.showToast(`Cancelamento do recibo #${ultimo.reciboId} desfeito. ${totalRevertido} item(s) restaurado(s).`, 'success');
});
};
window.gerarPDFReciboSpacecomSalvo = async (reciboId) => {
const registros = (window.dbData.movimentosManuais || [])
.filter(m => m.tipo === 'Saída Spacecom para Unidade' && (m.recibo || m.id) === reciboId);
const itens = registros
.map(m => ({
item: m.item,
qtd: parseInt(m.qtd, 10) || 0,
seriais: m.seriais || m.serialsBipados || ''
}));
if (itens.length === 0) return;
const registroAceito = registros.find(r => r.aceitePor) || null;
const registroBase = registros[0] || {};
await window.gerarPdfSpacecom(reciboId, 'Saída para Unidade', itens, {
incluirAssinaturaEletronica: true,
emissor: {
nome: window.currentUser?.nome || 'Colaborador Spacecom',
masp: window.currentUser?.masp || '',
cpf: window.currentUser?.cpf || '',
dataHora: new Date().toLocaleString('pt-BR')
},
recebedor: registroAceito ? {
nome: registroAceito.aceitePor || '',
masp: registroAceito.aceiteMasp || '',
cpf: registroAceito.aceiteCpf || '',
unidade: registroAceito.aceiteUnidade || registroBase.unidadeDestino || registroBase.unidade || '',
dataHora: registroAceito.aceiteData || ''
} : null
});
};
window.reciboSpacecomEdicaoBuffer = [];
window.atualizarQtdLinhaEdicaoReciboSpacecom = function(idx) {
const qtdEl = document.getElementById(`edit-recibo-item-qtd-${idx}`);
const seriaisEl = document.getElementById(`edit-recibo-item-seriais-${idx}`);
const exige = document.getElementById(`edit-recibo-item-exige-${idx}`)?.value === '1';
if (!qtdEl || !seriaisEl || !exige) return;
const lista = expandirSeriais(seriaisEl.value || '').filter(s => s && s !== 'Nenhum serial informado / Range omitido');
qtdEl.value = String(lista.length);
};
window.editarReciboSpacecom = function(reciboId) {
const registros = (window.dbData.movimentosManuais || []).filter(m => m.tipo === 'Saída Spacecom para Unidade' && (m.recibo || m.id) === reciboId);
if (registros.length === 0) {
window.showToast('Recibo não encontrado para edição.', 'error');
return;
}
const primeiro = registros[0] || {};
const destinoAtual = primeiro.unidadeDestino || primeiro.unidade || 'Central BH';
const origemAtual = primeiro.origemConferencia || 'Retorno de Manutenção (Limpos)';
window.reciboSpacecomEdicaoBuffer = registros.map(r => ({ ...r }));
const idEl = document.getElementById('edit-recibo-spacecom-id');
const destinoEl = document.getElementById('edit-recibo-spacecom-destino');
const origemEl = document.getElementById('edit-recibo-spacecom-origem');
const itensEl = document.getElementById('edit-recibo-spacecom-itens');
if (!idEl || !destinoEl || !origemEl || !itensEl) return;
idEl.value = reciboId;
destinoEl.value = destinoAtual;
origemEl.value = origemAtual;
let html = '';
window.reciboSpacecomEdicaoBuffer.forEach((reg, idx) => {
const exigeSerial = window.itemSpacecomExigeSerial ? window.itemSpacecomExigeSerial(reg.item) : ['TZPR', 'UPR', 'CM', 'TRAVAS'].includes(String(reg.item || '').toUpperCase());
const seriais = String(reg.seriais || reg.serialsBipados || '').trim();
const qtdInicial = parseInt(reg?.qtd ?? reg?.quantidade ?? 0, 10) || 0;
html += `
`;
});
itensEl.innerHTML = html;
openModal('modal-editar-recibo-spacecom');
lucide.createIcons();
};
window.salvarEdicaoReciboSpacecom = function() {
const reciboId = document.getElementById('edit-recibo-spacecom-id')?.value;
const destino = (document.getElementById('edit-recibo-spacecom-destino')?.value || '').trim();
const origem = (document.getElementById('edit-recibo-spacecom-origem')?.value || '').trim();
if (!reciboId) return;
if (!destino) {
window.showToast('Informe o destino do recibo.', 'error');
return;
}
if (!origem) {
window.showToast('Informe a origem do material.', 'error');
return;
}
const registros = (window.dbData.movimentosManuais || []).filter(m => m.tipo === 'Saída Spacecom para Unidade' && (m.recibo || m.id) === reciboId);
if (registros.length === 0) {
window.showToast('Recibo não encontrado para salvar edição.', 'error');
return;
}
const serialSet = new Set();
for (let idx = 0; idx < registros.length; idx++) {
const reg = registros[idx];
const exigeSerial = document.getElementById(`edit-recibo-item-exige-${idx}`)?.value === '1';
const seriaisTxt = (document.getElementById(`edit-recibo-item-seriais-${idx}`)?.value || '').trim();
const qtdInformada = parseInt(document.getElementById(`edit-recibo-item-qtd-${idx}`)?.value || '0', 10) || 0;
const lista = expandirSeriais(seriaisTxt).filter(s => s && s !== 'Nenhum serial informado / Range omitido');
if (exigeSerial && lista.length === 0) {
window.showToast(`O item ${window.formatItemName(reg.item)} exige seriais.`, 'error');
return;
}
if (exigeSerial) {
for (const s of lista) {
const k = String(s).trim().toUpperCase();
if (serialSet.has(k)) {
window.showToast(`Serial duplicado no recibo: ${s}`, 'error');
return;
}
serialSet.add(k);
}
}
reg.seriais = seriaisTxt;
reg.serialsBipados = seriaisTxt;
reg.qtd = exigeSerial ? lista.length : qtdInformada;
reg.unidadeDestino = destino;
reg.unidade = destino;
reg.origemConferencia = origem;
reg.obs = `Recibo Spacecom Nº ${reciboId}. Origem Material: ${origem}. Destino: ${destino}.`;
reg.editadoPor = window.currentUser?.nome || 'USUÁRIO';
reg.editadoEm = new Date().toLocaleString('pt-BR');
}
saveToCloud(['movimentosManuais']).then(() => {
closeModal('modal-editar-recibo-spacecom');
window.abrirModalHistoricoRecibosSpacecom();
window.showToast(`Recibo ${reciboId} atualizado com sucesso.`, 'success');
});
};
window.abrirModalHistoricoEnvios = () => {
const tb = $('tbody-historico-envios'); tb.innerHTML = '';
let list = (window.obterListaTransferenciasMescladaHistoricoLocal ? window.obterListaTransferenciasMescladaHistoricoLocal() : (window.dbData.transferencias || [])).filter(t => t.destino !== 'Central BH').slice().reverse();
if(currentUser.role === 'nucleo') {
const unidadesLower = (currentUser.unidades||[]).map(u => (u || '').trim().toLowerCase());
list = list.filter(t => {
const origemLower = (t.origem || '').trim().toLowerCase();
const destinoLower = (t.destino || '').trim().toLowerCase();
return unidadesLower.includes(destinoLower) || unidadesLower.includes(origemLower);
});
}
if(list.length === 0) {
tb.innerHTML = `Nenhum envio localizado no histórico completo. `;
} else {
list.forEach(t => {
let descItens = t.itens ? t.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item)}`).join(', ') : `${t.qtd}x ${window.formatItemName(t.item)}`;
let dataPed = t.data ? formatDate(t.data) : "-";
let recInfo = t.aceitePor ? `${t.aceitePor}${t.aceiteData} ` : "-";
const statusTxt = t.arquivado ? 'Arquivado' : String(t.status || 'Ativo');
tb.innerHTML += `
${dataPed}
#${t.id} ${t.destino} ${statusTxt}
${descItens}${t.observacoesSolicitante ? `${String(t.observacoesSolicitante).replace(//g, '>')} ` : ''}
${recInfo}
`;
});
}
openModal('modal-historico-envios'); lucide.createIcons();
};
window.abrirModalHistoricoPedidos = () => {
const tb = $('tbody-historico-pedidos'); tb.innerHTML = '';
let list = (window.obterListaTransferenciasMescladaHistoricoLocal ? window.obterListaTransferenciasMescladaHistoricoLocal() : (window.dbData.transferencias || [])).filter(t => t.destino !== 'Central BH').slice().reverse();
// Cada unidade visualiza APENAS seus próprios pedidos
if(currentUser.role === 'nucleo' || currentUser.role === 'spacecom') {
const unidadesCanonicas = (currentUser.unidades||[]).map(u => window.obterChaveCanonicaUnidade(u)).filter(Boolean);
list = list.filter(t => {
// Mostrar pedidos onde DESTINO é uma das unidades do usuário
return window.unidadePertenceAoUsuario(t.destino, unidadesCanonicas);
});
}
if(list.length === 0) {
tb.innerHTML = `Nenhuma solicitação encontrada no histórico das suas unidades. `;
} else {
list.forEach(t => {
let descItens = t.itens ? t.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item)}`).join(', ') : `${t.qtd}x ${window.formatItemName(t.item)}`;
let dataPed = t.data ? formatDate(t.data) : "-";
let solicitanteInfo = t.registradoPor ? `${t.registradoPor}
MASP: ${t.registradoMasp || '-'}
` : "N/A";
if (t.analisadoPor || (t.status !== 'Pendente' && t.status !== 'Cancelado')) {
let nomeAuth = t.analisadoPor || 'Juliana Aparecida de Camargos da Silva';
let maspAuth = t.analisadoMasp || '-';
let cargoAuth = t.analisadoCargo || (String(nomeAuth).toUpperCase().includes('JULIANA') ? 'Subdiretora de Gestão e Monitoramento Eletrônico' : 'Policial Penal / Gestão');
solicitanteInfo += ` Autorizado por:${nomeAuth}
MASP: ${maspAuth}
${cargoAuth}
`;
}
let dataSolicitacao = t.dataHoraRegistro || t.dataHoraAnalise || t.data || "-";
const status = String(t.status || 'N/A').trim();
tb.innerHTML += `
${dataPed}
#${t.id} ${t.destino} Status: ${status}${t.arquivado ? ' (Arquivado)' : ''}
${descItens}
${solicitanteInfo}
${dataSolicitacao}
${status !== 'Pendente' ? ` ` : ''}
`;
});
}
openModal('modal-historico-pedidos'); lucide.createIcons();
};
window.gerarPDFHistoricoTriagem = () => {
let list = (window.dbData.movimentosManuais || []).filter(m => m.tipo === 'Saída para Triagem/Manutenção').slice().reverse();
if(list.length === 0) { alert("Sem dados."); return; }
let bD = list.map(t => [t.data.split(' ')[0], t.responsavel, t.obs.split('Origem:')[1] || '-', `${t.qtd}x ${t.item}`, t.obs.split('.')[0]]);
window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'landscape', titulo: "CONSOLIDAÇÃO: HISTÓRICO GERENCIAL GERAL DOS ENVIOS SPACECOM", subtitulo: `Extraído em: ${new Date().toLocaleString('pt-BR')} por ${currentUser.nome} (MASP: ${currentUser.masp})`, head: ['Data', 'Responsável Final', 'Origem Relatada PPMG', 'Equipamentos Despachados', 'Nº Do Recibo'], body: bD, nomeArquivo: `Historico_Envios_Spacecom_${new Date().getTime()}.pdf` });
};
window.normalizarDataMovParaISO = function(txt) {
const bruto = String(txt || '').trim();
if (!bruto) return null;
const soData = bruto.split(' ')[0].replace(',', '').trim();
if (/^\d{4}-\d{2}-\d{2}$/.test(soData)) return soData;
const m = soData.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
if (m) return `${m[3]}-${m[2]}-${m[1]}`;
return null;
};
window.calcularImpactosEstoquePeriodo = function(inicioISO, fimISO) {
const inicio = inicioISO;
const fim = fimISO;
const impactos = [];
const addImpacto = (dataISO, recibo, item, qtd, delta, evento, origem) => {
if (!dataISO || dataISO < inicio || dataISO > fim) return;
impactos.push({ dataISO, recibo, item, qtd, delta, evento, origem });
};
(window.dbData.movimentosManuais || []).forEach(m => {
const tipo = String(m.tipo || '');
const recibo = String(m.recibo || m.id || 'S/N');
const item = String(m.item || 'N/A');
const qtd = parseInt(m.qtd || 0, 10) || 0;
const unidadeMov = String(m.unidade || m.destino || '').trim();
const motivo = ((m.obs || '').match(/Motivo:\s*([^.]+)/)?.[1] || '').trim().toUpperCase();
const dataMovISO = window.normalizarDataMovParaISO(m.data);
const dataCancelISO = window.normalizarDataMovParaISO(m.canceladoEm);
const dataRefeitoISO = window.normalizarDataMovParaISO(m.refeitoEm);
// Regras de movimentacao base para Central BH
if (tipo === 'Saída para Triagem/Manutenção') {
const baixaPermitida = ['DEFEITO', 'EQUIPAMENTO_NOVO', 'NOVO'].includes(motivo);
const origemCentral = unidadeMov === 'Central BH' || unidadeMov === 'Admissão/NSI';
if (baixaPermitida && origemCentral) {
addImpacto(dataMovISO, recibo, item, qtd, -qtd, 'Saída Triagem (baixa)', unidadeMov);
}
}
if (tipo === 'Entrada de Novos (Enviado pela Central)' || tipo === 'Entrada de Novos (Fornecedor)') {
if (unidadeMov === 'Central BH') {
addImpacto(dataMovISO, recibo, item, qtd, qtd, 'Entrada Central', unidadeMov);
}
}
if (tipo === 'Retorno de Manutenção (Limpos)') {
// Regra atual: retorno de triagem para Central BH nao movimenta estoque.
if (unidadeMov !== 'Central BH') {
addImpacto(dataMovISO, recibo, item, qtd, qtd, 'Retorno para unidade', unidadeMov);
}
}
// Estornos por cancelamento e refazer (impactam Central BH)
if (tipo === 'Saída para Triagem/Manutenção') {
const origemCentral = unidadeMov === 'Central BH' || unidadeMov === 'Admissão/NSI';
if (origemCentral && dataCancelISO) {
addImpacto(dataCancelISO, recibo, item, qtd, qtd, 'Cancelamento Triagem (estorno)', unidadeMov);
}
if (origemCentral && dataRefeitoISO) {
addImpacto(dataRefeitoISO, recibo, item, qtd, -qtd, 'Refazer Cancelamento Triagem', unidadeMov);
}
}
if (tipo === 'Saída Spacecom para Unidade') {
if (dataCancelISO) {
addImpacto(dataCancelISO, recibo, item, qtd, qtd, 'Cancelamento Recibo Spacecom (estorno)', 'Central BH');
}
if (dataRefeitoISO) {
addImpacto(dataRefeitoISO, recibo, item, qtd, -qtd, 'Refazer Cancelamento Recibo Spacecom', 'Central BH');
}
}
});
const porRecibo = {};
impactos.forEach(i => {
if (!porRecibo[i.recibo]) porRecibo[i.recibo] = { recibo: i.recibo, delta: 0, eventos: 0 };
porRecibo[i.recibo].delta += i.delta;
porRecibo[i.recibo].eventos += 1;
});
const hojeISO = new Date().toISOString().slice(0, 10);
const netHoje = impactos.filter(i => i.dataISO === hojeISO).reduce((s, i) => s + i.delta, 0);
const totalAtualCentral = Object.values(window.dbData.inventory || {}).reduce((s, it) => s + (parseInt(it?.atual || 0, 10) || 0), 0);
const aberturaHoje = totalAtualCentral - netHoje;
return {
impactos,
porRecibo: Object.values(porRecibo).sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta)),
hojeISO,
totalAtualCentral,
netHoje,
aberturaHoje
};
};
window.relatorioImpactoEstoqueOntemHoje = function() {
const inicio = '2026-07-01';
const fim = '2026-07-02';
const rel = window.calcularImpactosEstoquePeriodo(inicio, fim);
const listaRecibos = rel.porRecibo.length
? rel.porRecibo.map(r => `- ${r.recibo}: saldo ${r.delta > 0 ? '+' : ''}${r.delta} (eventos: ${r.eventos})`).join('\n')
: 'Nenhum recibo com impacto detectado no período.';
const resumo = [
`Período: ${inicio} a ${fim}`,
`Saldo atual Central BH: ${rel.totalAtualCentral}`,
`Variação hoje (${rel.hojeISO}): ${rel.netHoje > 0 ? '+' : ''}${rel.netHoje}`,
`Saldo de abertura de hoje: ${rel.aberturaHoje}`,
'',
'Recibos com impacto no estoque:',
listaRecibos
].join('\n');
console.table(rel.impactos);
alert(resumo);
return rel;
};
window.gerarPDFHistoricoDevolucoes = () => {
let list = (window.dbData.transferencias || []).filter(t => t.destino === 'Central BH').reverse();
if(currentUser.role === 'nucleo') {
const unidadesLower = (currentUser.unidades||[]).map(u => (u || '').trim().toLowerCase());
list = list.filter(t => unidadesLower.includes((t.origem || '').trim().toLowerCase()));
}
if(list.length === 0) { alert("Nenhum dado de devolução encontrado."); return; }
let bD = []; list.forEach(t => { let descItens = t.itens ? t.itens.map(it => `${it.qtd}x ${window.formatItemName(it.item)}`).join(', ') : `${t.qtd}x ${window.formatItemName(t.item)}`; let dt = t.data ? formatDate(t.data) : "-"; bD.push([t.id, dt, t.origem, descItens, t.status]); });
window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'portrait', titulo: "RELATÓRIO DE DEVOLUÇÕES À CENTRAL (REVERSA)", subtitulo: `Data: ${new Date().toLocaleString('pt-BR')} | Unidade: ${currentUser.role === 'nucleo' ? (currentUser.unidades||[])[0] : 'Central BH'}`, head: ['Cód', 'Data', 'Origem', 'Itens', 'Status'], body: bD, nomeArquivo: `Relatorio_Devolucoes_SIGME_${new Date().getTime()}.pdf` });
};
window.gerarPDFHistoricoAuditoria = () => {
let list = window.dbData.auditorias || [];
if(list.length === 0) { alert("Sem dados para exportar PDF."); return; }
let bD = list.map(a => [a.data.split(' ')[0], a.responsavel, a.origem || 'NÃO INFORMADA', a.qtd, (a.seriais||[]).join(', ')]);
window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'landscape', titulo: "ESTATÍSTICA OFICIAL DAS AUDITORIAS FINANCEIRAS (ABATES LEGAIS DE PAGOS)", subtitulo: `Gerado em: ${new Date().toLocaleString('pt-BR')} por ${currentUser.nome}`, head: ['Datação', 'Auditor Responsável Governamental', 'Origem Relatada', 'Qtd Abatida', 'Mural Rasteável de Seriais Abatidos'], body: bD, nomeArquivo: `TabelaAuditoria_SIGME_${new Date().getTime()}.pdf` });
};
window.gerarPDFOutroEstado = async (id) => {
const oe = window.dbData.outrosEstados.find(x => x.id === id); if (!oe) return;
const bD = [[oe.data.split(' ')[0], oe.serial, oe.estado, oe.orgao, oe.endereco]];
window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'landscape', titulo: "TERMO DE ACAUTELAMENTO DE EQUIPAMENTO EXTERNO", subtitulo: `Data de Registro: ${oe.data} | Registrado por: ${oe.registradoPor}`, head: ['Data', 'Serial / ID', 'Estado de Origem', 'Órgão', 'Endereço'], body: bD, nomeArquivo: `Acautelamento_${oe.serial}.pdf` });
};
window.gerarPDFAuditoriaSalva = async (id) => {
const a = window.dbData.auditorias.find(x => x.id === id); if (!a) return;
const seqStr = (a.seq || 1).toString().padStart(2, '0');
const displayRecibo = "RECIBO DE EQUIPAMENTOS PAGOS " + seqStr;
const itensConfigPdf = [{ item: a.item || "Equipamento Auditado", qtd: a.qtd, seriais: a.seriais || [], cols: 2, fs: 8 }];
window.gerarPdfUniversal({
tipoLayout: 'TRIAGEM', orientacao: 'portrait', isDevolucao: false, origem: a.origem || 'NÃO INFORMADA', numeroRecibo: displayRecibo, tipoRecibo: "", observacoes: "2ª Via. Documento regerado a partir do histórico de auditorias.",
itens: itensConfigPdf, nomeRepassador: a.responsavel, maspRepassador: a.masp || "N/A", lotacaoRepassador: "PPMG-CD/DME",
cidade: "Belo Horizonte", nomeArquivo: `Guia_Oficial_Auditoria_${seqStr}.pdf`
});
};
window.gerarPDFTriagemSalva = async (id) => {
const mov = window.dbData.movimentosManuais.find(x => x.id === id); if (!mov) return;
let numRecibo = "S/N";
const match = mov.obs.match(/Recibo Nº ([\w\d]+)/);
if (match) numRecibo = match[1];
let allItems = [];
if (numRecibo !== "S/N") {
const rel = window.dbData.movimentosManuais.filter(m => m.tipo === 'Saída para Triagem/Manutenção' && m.obs.includes(`Recibo Nº ${numRecibo}`));
rel.forEach(r => {
let listaSeriais = [];
if (r.seriais || r.serialsBipados) {
listaSeriais = expandirSeriais(r.seriais || r.serialsBipados).filter(s => s && s !== "Nenhum serial informado / Range omitido");
}
if (listaSeriais.length === 0) {
listaSeriais = ["Seriais não registrados neste recibo."];
}
allItems.push({ item: r.item, qtd: r.qtd, seriais: listaSeriais });
});
} else {
let listaSeriais = [];
if (mov.seriais || mov.serialsBipados) {
listaSeriais = expandirSeriais(mov.seriais || mov.serialsBipados).filter(s => s && s !== "Nenhum serial informado / Range omitido");
}
if (listaSeriais.length === 0) listaSeriais = ["Seriais indisponíveis no registro histórico."];
allItems.push({ item: mov.item, qtd: mov.qtd, seriais: listaSeriais });
}
let origemFato = "NÃO INFORMADA"; let unid = "Central BH";
let matFato = mov.obs.match(/Origem Fato: (.+)/); if (matFato) origemFato = matFato[1].trim();
let matUnid = mov.obs.match(/Unidade: ([^.]+)/); if (matUnid) unid = matUnid[1].trim();
const cid = unid !== "Central BH" ? unid.replace("Núcleo ", "").replace("Subnúcleo ", "").replace("Extensão ", "") : "Belo Horizonte";
await window.gerarPdfUniversal({
tipoLayout: 'TRIAGEM', origem: origemFato, numeroRecibo: numRecibo, itens: allItems, observacoes: "2ª Via. Documento regerado a partir do histórico logístico.",
seriaisObj: {}, nomeRepassador: mov.responsavel, maspRepassador: "N/A",
lotacaoRepassador: "PPMG-CD/DME",
cidade: cid, nomeArquivo: `Recibo_n_${numRecibo}_para_triagem_SPACECOM_2Via.pdf`
});
};
window.verificarNecessidadeSeriaisConf = () => {
let needsSerials = false;
itensConfig.forEach(i => {
if (i !== 'CINTAS' && i !== 'FONTE' && !i.startsWith('MANUAL')) {
const el = $(`conf-qtd-${i}`);
if (el && parseInt(el.value, 10) > 0) needsSerials = true;
}
});
const div = $('conf-seriais-div');
if (div) {
if (needsSerials) { div.classList.remove('hidden'); div.classList.add('grid'); }
else { div.classList.add('hidden'); div.classList.remove('grid'); }
}
};
window.mudarOrigemConferencia = () => {
window.activeConferenciaTransferId = null;
const divEsperados = $('conf-div-esperados');
if(divEsperados) divEsperados.classList.remove('opacity-30', 'pointer-events-none');
if($('conf-esperados')) {
$('conf-esperados').placeholder = 'Cole os seriais da nota ou importe arquivo...';
}
};
window.renderDestinos = () => {
const o = $('conf-origem').value;
const d = $('conf-destino');
let h = '';
if (currentUser.role === 'spacecom') {
d.innerHTML = 'Equipamentos triados ';
return;
}
// Repopula as unidades padrão caso não seja o caso acima
if(['admin', 'central'].includes(currentUser.role)) {
h += `Estoque Central BH `;
nucleosNames.forEach(n => h += `${n} `);
} else {
const unidadesPermitidas = currentUser.role === 'nucleo' && typeof window.obterUnidadesHierarquicasUsuario === 'function'
? window.obterUnidadesHierarquicasUsuario()
: (currentUser.unidades||[]);
unidadesPermitidas.forEach(n => h += `${n} `);
}
d.innerHTML = h;
};
window.calcularPareamentosProvaveisSeriais = (sobrandoList = [], faltantesList = [], maxDist = 2) => {
const distanciaHamming = (a, b) => {
if (!a || !b || a.length !== b.length) return Number.POSITIVE_INFINITY;
let d = 0;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) d++;
return d;
};
const usadosFaltantes = new Set();
const pares = [];
sobrandoList.forEach((serialBipado) => {
let melhorIdx = -1;
let melhorDist = Number.POSITIVE_INFINITY;
for (let i = 0; i < faltantesList.length; i++) {
if (usadosFaltantes.has(i)) continue;
const serialRecibo = faltantesList[i];
const dist = distanciaHamming(String(serialBipado), String(serialRecibo));
if (dist < melhorDist) {
melhorDist = dist;
melhorIdx = i;
}
}
if (melhorIdx > -1 && melhorDist <= maxDist) {
usadosFaltantes.add(melhorIdx);
pares.push({
bipado: serialBipado,
recibo: faltantesList[melhorIdx],
distancia: melhorDist
});
}
});
return pares;
};
window.verificarConferencia = () => {
const origem = $('conf-origem').value; const resDiv = $('conf-resultado'); const btnConf = $('btn-confirmar-entrada');
let itensSelecionados = []; let sumSerialsExpected = 0;
itensConfig.forEach(i => { const q = parseInt($(`conf-qtd-${i}`).value, 10) || 0; if (q > 0) { itensSelecionados.push({ item: i, qtd: q }); if (i !== 'CINTAS' && i !== 'FONTE' && !i.startsWith('MANUAL')) sumSerialsExpected += q; } });
if (itensSelecionados.length === 0) { alert("Informe a quantidade de pelo menos um equipamento."); return; }
if (sumSerialsExpected === 0) {
resDiv.className = 'p-5 rounded-2xl text-[11px] md:text-xs font-mono whitespace-pre-wrap max-h-[180px] overflow-y-auto custom-scroll border border-green-500 bg-green-900/20 text-green-400 mt-4 shadow-inner leading-relaxed';
let textRes = "✅ SUCESSO!\n\nMaterial sem necessidade de verificação ótica.\nItens validados:\n"; itensSelecionados.forEach(it => textRes += `- ${it.qtd}x ${window.formatItemName(it.item)}\n`); resDiv.innerHTML = textRes;
window.conferenciaItensValidados = itensSelecionados; resDiv.classList.remove('hidden'); btnConf.classList.remove('hidden'); return;
}
const esperadosText = $val('conf-esperados'); const bipadosText = $val('conf-bipados');
const listaBipados = expandirSeriais(bipadosText).filter(s => s !== "Nenhum serial informado / Range omitido");
if (!esperadosText || !bipadosText) { alert("Cole a listagem da nota e bipe o código com o leitor ótico."); return; }
const listaEsperados = expandirSeriais(esperadosText).filter(s => s !== "Nenhum serial informado / Range omitido");
const totalBrutoEsperados = listaEsperados.length;
const totalBrutoBipados = listaBipados.length;
const normalize = (s) => s.replace(/^0+/, '') || '0';
const mapEsperados = new Map(listaEsperados.map(s => [normalize(s), s]));
const mapBipados = new Map(listaBipados.map(s => [normalize(s), s]));
let faltantes = []; let sobrando = []; let validados = [];
mapEsperados.forEach((orig, norm) => { if (!mapBipados.has(norm)) faltantes.push(orig); });
mapBipados.forEach((orig, norm) => { if (!mapEsperados.has(norm)) sobrando.push(orig); else validados.push(orig); });
const paresProvaveis = window.calcularPareamentosProvaveisSeriais(sobrando, faltantes, 2);
resDiv.classList.remove('hidden'); window.falhasConferencia = faltantes; window.sobrasConferencia = sobrando; window.paresConferencia = paresProvaveis;
let baseHtml = '';
let canConfirm = false;
let isPerfect = (sobrando.length === 0 && faltantes.length === 0);
if (isPerfect) {
if (validados.length !== sumSerialsExpected) {
resDiv.className = 'p-5 rounded-2xl text-[10px] md:text-[11px] font-mono whitespace-pre-wrap max-h-[250px] overflow-y-auto custom-scroll border border-red-500 bg-red-900/20 text-red-500 mt-4 shadow-inner leading-relaxed';
resDiv.innerHTML = `🚨 ATENÇÃO!\n\nA conferência da Nota vs Físico bateu 100% (${validados.length} peças).\nPorém, você digitou nas caixas acima que daria entrada em ${sumSerialsExpected} peças com serial.\n\nPor favor, ajuste as caixas de quantidades para refletir a realidade da nota.`;
btnConf.classList.add('hidden'); return;
}
resDiv.className = 'p-5 rounded-2xl text-[11px] md:text-xs font-mono whitespace-pre-wrap max-h-[250px] overflow-y-auto custom-scroll border border-green-500 bg-green-900/20 text-green-400 mt-4 shadow-inner leading-relaxed';
let detalhesText = itensSelecionados.map(it => `${it.qtd} unidade(s) de ${window.formatItemName(it.item)}`).join(', ').replace(/,([^,]*)$/, " e$1");
resDiv.innerHTML = `✅ SUCESSO ABSOLUTO!\n\nInformo que a conferência física da última remessa recebida foi concluída com sucesso. Comparei os números de patrimônio listados na nota de envio da Spacecom com os números bipados individualmente na caixa. O resultado atesta que os equipamentos recolhidos são 100% compatíveis com os números da nota. Nenhuma divergência foi encontrada no lote contendo ${detalhesText}. Os itens estão liberados para armazenamento definitivo e registro no sistema.`;
canConfirm = true;
} else {
if (validados.length > 0 && validados.length !== sumSerialsExpected) {
resDiv.className = 'p-5 rounded-2xl text-[10px] md:text-[11px] font-mono whitespace-pre-wrap max-h-[250px] overflow-y-auto custom-scroll border border-red-500 bg-red-900/20 text-red-500 mt-4 shadow-inner leading-relaxed';
let detalhesDivergencia = `\n\nResumo da divergência:\n- Seriais válidos: ${validados.length}\n- Seriais fora da nota: ${sobrando.length}\n- Seriais faltantes da nota: ${faltantes.length}`;
if (totalBrutoEsperados === totalBrutoBipados) {
detalhesDivergencia += `\n\nℹ️ Observação: as duas caixas podem mostrar o mesmo total bruto (${totalBrutoEsperados}), mas ainda assim existir divergência de conteúdo entre os códigos.`;
}
if (sobrando.length > 0) {
detalhesDivergencia += `\n\n⚠️ Bipados que NÃO CONSTAM na nota:\n${sobrando.slice(0, 20).join(', ')}${sobrando.length > 20 ? ' ...' : ''}`;
}
if (faltantes.length > 0) {
detalhesDivergencia += `\n\n❌ Consta na nota, mas faltou bipar:\n${faltantes.slice(0, 20).join(', ')}${faltantes.length > 20 ? ' ...' : ''}`;
}
if (paresProvaveis.length > 0) {
let txtPares = '\n\nRELATÓRIO DE INCONSISTÊNCIA DE NUMERAÇÃO (PROVÁVEL):';
paresProvaveis.slice(0, 10).forEach(p => {
txtPares += `\n- Bipado (Físico): ${p.bipado} | Consta no Recibo: ${p.recibo}`;
});
txtPares += '\nOrigem provável: numeração incorreta no recibo/documento.';
detalhesDivergencia += txtPares;
}
resDiv.innerHTML = `🚨 ATENÇÃO: QUANTIDADE INCOMPATÍVEL!\n\nVocê bipou ${validados.length} peças válidas (que estão na nota).\nPorém, nas caixas de quantidade você colocou ${sumSerialsExpected}.\n\nTotais brutos digitados: Nota ${totalBrutoEsperados} | Físico ${totalBrutoBipados}.\n\nPara fazer a ENTRADA FRACIONADA apenas dos itens válidos, mude as quantidades lá em cima para EXATAMENTE o que você bipou de correto (${validados.length}), e clique em Verificar novamente.${detalhesDivergencia}`;
btnConf.classList.add('hidden'); return;
}
if (validados.length === 0) {
resDiv.className = 'p-5 rounded-2xl text-[10px] md:text-[11px] font-mono whitespace-pre-wrap max-h-[400px] overflow-y-auto custom-scroll border border-red-500 bg-red-900/20 text-red-200 mt-4 shadow-inner leading-relaxed';
baseHtml += `🚨 NENHUM ITEM VÁLIDO `;
canConfirm = false;
} else {
resDiv.className = 'p-5 rounded-2xl text-[10px] md:text-[11px] font-mono whitespace-pre-wrap max-h-[400px] overflow-y-auto custom-scroll border border-orange-500 bg-orange-900/20 text-orange-200 mt-4 shadow-inner leading-relaxed';
baseHtml += `⚠️ ENTRADA PARCIAL COM DIVERGÊNCIA `;
baseHtml += `✅ Bipados que ESTÃO na nota (Liberados): ${validados.length} equipamento(s) . `;
canConfirm = true;
}
baseHtml += `Total Esperado (Nota): ${mapEsperados.size} | Total Bipado (Caixa): ${mapBipados.size} `;
if (faltantes.length > 0) {
baseHtml += `❌ Consta na Nota, mas FALTOU BIPAR: ${faltantes.length} equipamento(s) . (${faltantes.slice(0, 50).join(', ')}${faltantes.length > 50 ? '...' : ''}) `;
}
if (sobrando.length > 0) {
baseHtml += `⚠️ Bipados que NÃO CONSTAM na nota (Bloqueados): ${sobrando.length} equipamento(s) . (${sobrando.slice(0, 50).join(', ')}${sobrando.length > 50 ? '...' : ''}) `;
}
if (paresProvaveis.length > 0) {
baseHtml += `🔎 Possível erro de numeração no recibo: `;
paresProvaveis.slice(0, 10).forEach(p => {
baseHtml += `Bipado: ${p.bipado} | Recibo: ${p.recibo} `;
});
baseHtml += `Recomendação: corrigir o recibo para refletir a leitura física antes de concluir. `;
}
if (canConfirm) {
baseHtml += `\nVocê pode confirmar a entrada apenas dos ${validados.length} equipamentos válidos. O restante não entrará no estoque e os faltantes continuarão pendentes na nota. `;
}
baseHtml += ` Baixar Relatório de Divergência `;
resDiv.innerHTML = baseHtml;
}
if (canConfirm) {
window.conferenciaItensValidados = itensSelecionados;
window.conferenciaBipados = validados;
btnConf.classList.remove('hidden');
} else {
window.conferenciaItensValidados = null;
btnConf.classList.add('hidden');
}
lucide.createIcons();
};
window.gerarPDFDivergencia = () => {
let bD = [];
if(window.paresConferencia && window.paresConferencia.length > 0) {
window.paresConferencia.forEach(p => bD.push(['PAREAMENTO PROVÁVEL', p.bipado, p.recibo, 'Possível erro de numeração no recibo']));
}
if(window.falhasConferencia) window.falhasConferencia.forEach(s => bD.push(['FALTANTE', '-', s, 'Consta no recibo, mas não foi bipado']));
if(window.sobrasConferencia) window.sobrasConferencia.forEach(s => bD.push(['SOBRA', s, '-', 'Bipado, mas não consta no recibo']));
if(bD.length === 0) return;
window.gerarPdfUniversal({
tipo: 'tabela_relatorio', orientacao: 'portrait',
titulo: 'RELATÓRIO DE INCONSISTÊNCIA DE ESTOQUE',
subtitulo: `Data: ${new Date().toLocaleString('pt-BR')} | Responsável: ${currentUser.nome} (${currentUser.masp})`,
head: ['Tipo', 'Leitura Bipada (Físico)', 'Número no Recibo', 'Observação'],
body: bD,
nomeArquivo: `Relatorio_Inconsistencia_${new Date().getTime()}.pdf`
});
};
window.confirmarEntradaConferida = () => {
if (!window.conferenciaItensValidados) return;
const itens = window.conferenciaItensValidados; const tipoEntrada = $('conf-origem').value; const destino = $('conf-destino').value;
const seriaisConferencia = (window.conferenciaBipados || []).filter(Boolean);
const seriaisConferenciaTxt = seriaisConferencia.join('\n');
const destinoResolvido = (destino === 'Central BH' || destino === 'Admissão/NSI')
? 'Central BH'
: (window.resolverChaveNucleo ? window.resolverChaveNucleo(destino) : destino);
let alterouEstoque = false;
let listaEsperadosRestantes = [];
if (window.conferenciaBipados && window.conferenciaBipados.length > 0) {
const bipadosSet = new Set(window.conferenciaBipados.map(s => s.replace(/^0+/, '') || '0'));
const esperadosText = $val('conf-esperados');
const listaEsperados = expandirSeriais(esperadosText).filter(s => s !== "Nenhum serial informado / Range omitido");
listaEsperadosRestantes = listaEsperados.filter(s => !bipadosSet.has(s.replace(/^0+/, '') || '0'));
}
let obsAdicional = "";
if (listaEsperadosRestantes.length > 0) {
obsAdicional = ` (Entrada Fracionada. Restam ${listaEsperadosRestantes.length} na nota)`;
}
itens.forEach(({ item, qtd }) => {
if (destinoResolvido === 'Central BH') {
if (!window.dbData.inventory[item]) window.dbData.inventory[item] = { atual: 0, ideal: 50, baixo: 15 };
window.dbData.inventory[item].atual += qtd;
} else {
if (!window.dbData.nucleosInventory[destinoResolvido]) {
window.dbData.nucleosInventory[destinoResolvido] = {};
itensConfig.forEach(i => window.dbData.nucleosInventory[destinoResolvido][i] = {atual:0, ideal:50, baixo:15});
}
if (!window.dbData.nucleosInventory[destinoResolvido][item]) {
window.dbData.nucleosInventory[destinoResolvido][item] = { atual: 0, ideal: 50, baixo: 15 };
}
window.dbData.nucleosInventory[destinoResolvido][item].atual += qtd;
}
alterouEstoque = true;
window.dbData.movimentosManuais.push({ id: "MOV-" + Date.now().toString().slice(-4) + Math.floor(Math.random()*100), data: new Date().toLocaleString('pt-BR'), item: item, tipo: tipoEntrada, qtd: qtd, unidade: destino, destino: destino, destinoResolvido: destinoResolvido, origemConferencia: tipoEntrada, contabilizadoEstoque: true, obs: `Entrada Operacional Oficial${obsAdicional}. Origem: ${tipoEntrada === 'Entrada de Novos (Enviado pela Central)' ? 'Central BH' : (tipoEntrada === 'Entrada de Novos (Fornecedor)' ? 'Fornecedor' : 'Spacecom Triagem')}. Destino: ${destino}.`, responsavel: currentUser.nome, seriais: seriaisConferenciaTxt, serialsBipados: seriaisConferenciaTxt, reciboRelacionado: window.activeConferenciaRecibo || null });
});
let colsToSave = ['movimentosManuais'];
if (alterouEstoque) colsToSave.push('inventory', 'nucleosInventory');
if (window.activeConferenciaTransferId) {
const t = window.dbData.transferencias.find(x => x.id === window.activeConferenciaTransferId);
if (t) {
t.status = 'Concluído';
t.aceitePor = currentUser.nome;
t.aceiteMasp = currentUser.masp;
t.aceiteData = new Date().toLocaleString('pt-BR');
colsToSave.push('transferencias');
}
}
if (window.activeConferenciaRecibo) {
(window.dbData.movimentosManuais || []).forEach(m => {
if (m.recibo === window.activeConferenciaRecibo) {
m.seriaisConferenciaUnidade = seriaisConferenciaTxt;
if (m.tipo === 'Saída Spacecom para Unidade') {
m.status = 'Recebido pela Unidade';
m.aceiteData = new Date().toLocaleString('pt-BR');
m.aceitePor = currentUser.nome;
m.aceiteMasp = currentUser.masp || '';
m.aceiteCpf = currentUser.cpf || '';
m.aceiteUnidade = destino;
} else {
m.status = 'Recebido pela Spacecom';
m.recebidoData = new Date().toLocaleString('pt-BR');
m.recebidoPor = currentUser.nome;
}
}
});
window.activeConferenciaRecibo = null;
}
saveToCloud(colsToSave).then(() => {
const msgEntrada = `Entrada de ${itens.length} tipo(s) de equipamento(s) contabilizada com sucesso no estoque de ${destinoResolvido}.`;
alert(`${msgEntrada}${window.activeConferenciaTransferId ? ' O pedido correspondente foi marcado como Concluído.' : ''}`);
if (listaEsperadosRestantes.length > 0) {
$('conf-esperados').value = listaEsperadosRestantes.join('\n');
alert(`Atenção: A lista de 'Seriais da Nota' foi atualizada e encurtada, restando apenas os ${listaEsperadosRestantes.length} equipamentos que ainda não receberam entrada.\n\nVocê pode continuar bipando a próxima caixa!`);
} else {
$('conf-esperados').value = '';
}
$('conf-bipados').value = '';
if(window.atualizarContagemSeriais) { window.atualizarContagemSeriais('conf-esperados'); window.atualizarContagemSeriais('conf-bipados'); }
itensConfig.forEach(i => { const el = $(`conf-qtd-${i}`); if(el) el.value = '0'; });
$('conf-resultado').classList.add('hidden'); $('btn-confirmar-entrada').classList.add('hidden');
window.conferenciaItensValidados = null; window.conferenciaBipados = null;
window.activeConferenciaTransferId = null;
if (destino === 'Central BH') renderDashboard(); else renderNucleoDashboard();
window.renderPainelExecutivo(); window.verificarNecessidadeSeriaisConf();
if (colsToSave.includes('transferencias')) renderKanban('suprimento');
const gerarPdf = true;
const loteRef = { id: window.activeConferenciaTransferId || "SISTEMA" };
const origem = tipoEntrada;
const itemsList = itens;
if(gerarPdf) {
if (currentUser.role === 'spacecom') {
window.gerarPdfSpacecom(loteRef.id, tipoEntrada, itens);
} else {
window.gerarRecibo(loteRef.id, origem, destino, itemsList);
}
}
});
};
window.reconciliarEntradasSpacecomPendentes = async (movimentoId = null) => {
const movimentos = window.dbData?.movimentosManuais || [];
const inventory = window.dbData?.inventory || {};
const nucleosInventory = window.dbData?.nucleosInventory || {};
let ajustesCentral = 0;
let qtdCentral = 0;
const itensCentral = {};
let ajustesInterior = 0;
const itensInterior = {};
const pendenciasInterior = [];
const idAlvo = movimentoId ? String(movimentoId).replace(/^#/, '').trim() : null;
const ehTipoEntradaConferencia = (tipo) => {
const t = String(tipo || '');
return t.includes('Retorno de Manutenção') || t.includes('Entrada de Novos (Enviado pela Central)') || t.includes('Entrada de Novos (Fornecedor)');
};
movimentos.forEach(m => {
const tipo = String(m?.tipo || '');
const item = String(m?.item || '');
const qtd = parseInt(m?.qtd || 0, 10) || 0;
const destinoOriginal = String(m?.destino || m?.unidade || '').trim();
const destinoMov = m?.destinoResolvido || (window.resolverChaveNucleo ? window.resolverChaveNucleo(destinoOriginal) : destinoOriginal);
const isEntradaOficial = String(m?.obs || '').includes('Entrada Operacional Oficial');
const isMovConferencia = (Boolean(m?.origemConferencia) || isEntradaOficial) && ehTipoEntradaConferencia(tipo);
const isIdAlvo = !idAlvo || String(m?.id || '').trim() === idAlvo;
if (!isMovConferencia || !isIdAlvo || !item || qtd <= 0) return;
if (m.contabilizadoEstoque === true || m._retroCentralLancado === true || m._retroInteriorLancado === true) return;
if (destinoMov === 'Central BH') {
if (!inventory[item]) inventory[item] = { atual: 0, ideal: 50, baixo: 15 };
inventory[item].atual += qtd;
m._retroCentralLancado = true;
m.contabilizadoEstoque = true;
ajustesCentral += 1;
qtdCentral += qtd;
itensCentral[item] = (itensCentral[item] || 0) + qtd;
return;
}
if (destinoMov !== 'Central BH') {
if (!destinoMov) {
pendenciasInterior.push({ id: m.id || '', item, qtd, destino: destinoOriginal || 'N/A', tipo });
return;
}
if (!nucleosInventory[destinoMov]) {
nucleosInventory[destinoMov] = {};
itensConfig.forEach(i => { nucleosInventory[destinoMov][i] = { atual: 0, ideal: 50, baixo: 15 }; });
}
if (!nucleosInventory[destinoMov][item]) {
nucleosInventory[destinoMov][item] = { atual: 0, ideal: 50, baixo: 15 };
}
nucleosInventory[destinoMov][item].atual += qtd;
m._retroInteriorLancado = true;
m.contabilizadoEstoque = true;
ajustesInterior += 1;
itensInterior[`${destinoMov}:${item}`] = (itensInterior[`${destinoMov}:${item}`] || 0) + qtd;
}
});
if (ajustesCentral > 0 || ajustesInterior > 0) {
movimentos.push({
id: 'AUD-' + Date.now().toString().slice(-6),
data: new Date().toLocaleString('pt-BR'),
item: 'MULTI',
tipo: 'Auditoria/Reconciliacao Spacecom',
qtd: qtdCentral,
unidade: idAlvo ? `Movimento ${idAlvo}` : 'Central BH',
destino: idAlvo ? 'Reconciliacao Pontual' : 'Central BH',
responsavel: (window.currentUser && window.currentUser.nome) ? window.currentUser.nome : 'SISTEMA',
obs: `Reconciliacao retroativa aplicada. Central: ${ajustesCentral} movimento(s), itens [${Object.entries(itensCentral).map(([k,v]) => `${k}:${v}`).join(', ') || 'nenhum'}]. Interior: ${ajustesInterior} movimento(s), itens [${Object.entries(itensInterior).map(([k,v]) => `${k}:${v}`).join(', ') || 'nenhum'}].`
});
await saveToCloud(['inventory', 'nucleosInventory', 'movimentosManuais']);
if (typeof renderDashboard === 'function') renderDashboard();
if (typeof renderNucleoDashboard === 'function') renderNucleoDashboard();
if (typeof window.renderPainelExecutivo === 'function') window.renderPainelExecutivo();
}
return {
ajustesCentral,
qtdCentral,
itensCentral,
ajustesInterior,
itensInterior,
pendenciasInterior
};
};
window.reprocessarEntradaNaoContabilizada = async (movimentoId = 'MOV-452674') => {
const id = String(movimentoId || '').replace(/^#/, '').trim();
if (!id) return { ok: false, motivo: 'ID de movimento invalido.' };
const existe = (window.dbData?.movimentosManuais || []).some(m => String(m?.id || '').trim() === id);
if (!existe) return { ok: false, motivo: `Movimento ${id} nao encontrado.` };
const resultado = await window.reconciliarEntradasSpacecomPendentes(id);
return { ok: true, movimentoId: id, ...resultado };
};
window._reconciliacaoMovCriticoAplicada = window._reconciliacaoMovCriticoAplicada || false;
window._reconciliacaoMovCriticoEmAndamento = window._reconciliacaoMovCriticoEmAndamento || false;
window.tentarReconciliarMovCritico = async () => {
if (window._reconciliacaoMovCriticoAplicada || window._reconciliacaoMovCriticoEmAndamento) return;
const existe = (window.dbData?.movimentosManuais || []).some(m => String(m?.id || '').trim() === 'MOV-452674');
if (!existe) return;
window._reconciliacaoMovCriticoEmAndamento = true;
try {
const r = await window.reprocessarEntradaNaoContabilizada('MOV-452674');
if (r && r.ok) window._reconciliacaoMovCriticoAplicada = true;
} catch (e) {
console.error('Falha ao reconciliar MOV-452674:', e);
} finally {
window._reconciliacaoMovCriticoEmAndamento = false;
}
};
window.adicionarItemTriagem = () => { const item = $('triagem-item').value; const qtd = parseInt($('triagem-qtd').value, 10); const seriais = $val('triagem-seriais'); if (isNaN(qtd) || qtd <= 0) { alert("Forneça a quantidade da peça."); return; } if (!seriais && item !== 'CINTAS' && item !== 'FONTE') { alert("Equipamento exige a inserção dos seriais óticos ou QR Code."); return; } if (item !== 'CINTAS' && item !== 'FONTE') { let listaExp = expandirSeriais(seriais); if (listaExp.length !== qtd) { if(!confirm(`Atenção: A quantidade informada (${qtd}) diverge do número de seriais lidos (${listaExp.length}). Deseja adicionar mesmo assim?`)) { return; } } } window.triagemItensPendentes.push({ item, qtd, seriais }); $('triagem-seriais').value = ''; $('triagem-qtd').value = '1'; if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('triagem-seriais'); atualizarListaTriagem(); };
function atualizarListaTriagem() { const listaDiv = $('triagem-lista'); listaDiv.innerHTML = ''; if (window.triagemItensPendentes.length === 0) { listaDiv.innerHTML = 'A urna de armazenamento deste malote virtual encontra-se vazia.
'; return; } window.triagemItensPendentes.forEach((it, index) => { listaDiv.innerHTML += `${it.qtd}x ${it.item}
`; }); lucide.createIcons(); }
window.removerItemTriagem = (index) => { window.triagemItensPendentes.splice(index, 1); atualizarListaTriagem(); };
window.adicionarItemAuditoria = () => {
const item = $('auditoria-item').value; const seriais = $val('retornados-list');
if (!seriais) { alert("Bipe os equipamentos ou cole os seriais antes de adicionar."); return; }
const lista = expandirSeriais(seriais).filter(s => s && s !== "Nenhum serial informado / Range omitido");
if(lista.length === 0) { alert("Nenhum serial válido detectado."); return; }
window.auditoriaItensPendentes.push({ item, qtd: lista.length, seriais });
$('retornados-list').value = ''; if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('retornados-list');
atualizarListaPouchAuditoria();
};
window.removerItemAuditoria = (index) => { window.auditoriaItensPendentes.splice(index, 1); atualizarListaPouchAuditoria(); };
window.adicionarItemExpectativa = () => {
const item = $('expectativa-item').value; const seriais = $val('expectativa-list');
if (!seriais) { alert("Informe os seriais antes de adicionar."); return; }
const lista = expandirSeriais(seriais).filter(s => s && s !== "Nenhum serial informado / Range omitido");
if(lista.length === 0) { alert("Nenhum serial válido detectado."); return; }
window.expectativaItensPendentes.push({ item, qtd: lista.length, seriais });
$('expectativa-list').value = ''; if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('expectativa-list');
atualizarListaPouchExpectativa();
};
window.removerItemExpectativa = (index) => { window.expectativaItensPendentes.splice(index, 1); atualizarListaPouchExpectativa(); };
function atualizarListaPouchExpectativa() {
const listaDiv = $('expectativa-pouch-list'); if(!listaDiv) return; listaDiv.innerHTML = '';
if (window.expectativaItensPendentes.length === 0) { listaDiv.innerHTML = 'Malote de recibo vazio.
'; return; }
window.expectativaItensPendentes.forEach((it, index) => {
listaDiv.innerHTML += `${it.qtd}x ${it.item} ${it.seriais}
`;
}); lucide.createIcons();
}
function atualizarListaPouchAuditoria() {
const listaDiv = $('auditoria-pouch-list'); if(!listaDiv) return; listaDiv.innerHTML = '';
if (window.auditoriaItensPendentes.length === 0) { listaDiv.innerHTML = 'Malote de triagem vazio.
'; return; }
window.auditoriaItensPendentes.forEach((it, index) => {
listaDiv.innerHTML += `${it.qtd}x ${it.item} ${it.seriais}
`;
}); lucide.createIcons();
}
window.gerarPDFTriagemESaida = async () => {
const unidadeBaixa = $('triagem-unidade').value;
const motivo = $('triagem-motivo').value;
const motivoNorm = String(motivo || '').trim().toUpperCase();
const deveDarBaixaEstoque = ['DEFEITO', 'EQUIPAMENTO_NOVO', 'NOVO'].includes(motivoNorm);
const origem = $val('triagem-origem') || "NÃO INFORMADA";
if (window.triagemItensPendentes.length === 0) { alert("Adicione pelo menos um volume ao malote."); return; }
const getUnitPrefix = (u) => {
if (u === 'Admissão/NSI') return 'NSI';
if (u === 'Central BH') return 'BH';
let clean = u.replace(/Núcleo |Subnúcleo |Extensão /gi, '').trim().toUpperCase();
let words = clean.split(' ').filter(w => w.length > 2);
if (words.length === 1) return words[0].substring(0, 3);
return words.map(w => w[0]).join('').substring(0, 3);
};
const prefix = getUnitPrefix(unidadeBaixa);
let maxSeq = 0;
(window.dbData.movimentosManuais || []).forEach(m => {
if (m.tipo === 'Saída para Triagem/Manutenção' && m.obs && m.obs.includes(`Recibo Nº ${prefix}`)) {
const regex = new RegExp(`Recibo Nº ${prefix}(\\d+)`);
const match = m.obs.match(regex);
if (match) {
const seq = parseInt(match[1], 10);
if (seq > maxSeq) maxSeq = seq;
}
}
});
const numeroRecibo = prefix + (maxSeq + 1).toString().padStart(2, '0');
let sObj = {};
window.triagemItensPendentes.forEach(it => {
const seriaisNormalizados = (it.seriais || '').trim();
if (deveDarBaixaEstoque) {
if (unidadeBaixa === 'Central BH' || unidadeBaixa === 'Admissão/NSI') {
if (window.dbData.inventory[it.item]) window.dbData.inventory[it.item].atual -= parseInt(it.qtd, 10);
} else {
if (window.dbData.nucleosInventory[unidadeBaixa] && window.dbData.nucleosInventory[unidadeBaixa][it.item]) {
window.dbData.nucleosInventory[unidadeBaixa][it.item].atual -= parseInt(it.qtd, 10);
}
}
}
window.dbData.movimentosManuais.push({ id: "MOV-" + Date.now().toString().slice(-4), data: new Date().toLocaleString('pt-BR'), item: it.item, tipo: 'Saída para Triagem/Manutenção', qtd: it.qtd, obs: `Expedição De Rota Lançada. Recibo Nº ${numeroRecibo}. Motivo: ${motivo}. Unidade: ${unidadeBaixa}. Origem Fato: ${origem}`, responsavel: currentUser.nome, unidade: unidadeBaixa, status: 'Pendente', recibo: numeroRecibo, seriais: seriaisNormalizados, serialsBipados: seriaisNormalizados });
sObj[it.item] = it.seriais;
});
saveToCloud(['movimentosManuais', 'inventory', 'nucleosInventory']).then(() => {
if (unidadeBaixa === 'Central BH') renderDashboard(); else renderNucleoDashboard();
window.renderPainelExecutivo();
});
const cid = unidadeBaixa !== "Central BH" ? unidadeBaixa.replace("Núcleo ", "").replace("Subnúcleo ", "").replace("Extensão ", "") : "Belo Horizonte";
const obs = prompt("Deseja inserir alguma observação neste recibo de triagem? (Deixe em branco se não)");
await window.gerarPdfUniversal({
tipoLayout: 'TRIAGEM', origem: origem, numeroRecibo: numeroRecibo.toString(), itens: window.triagemItensPendentes, observacoes: obs,
seriaisObj: sObj, nomeRepassador: currentUser.nome, maspRepassador: currentUser.masp,
lotacaoRepassador: currentUser.role === 'nucleo' ? (currentUser.unidades||[])[0] : 'PPMG-CD/DME',
cidade: cid, nomeArquivo: `Recibo_n_${numeroRecibo}_para_triagem_SPACECOM.pdf`
});
window.triagemItensPendentes = []; $('triagem-origem').value = ''; atualizarListaTriagem(); alert("Saída registrada com sucesso. PDF gerado.");
};
window.salvarListaSac = () => { localStorage.setItem('ppmg_sac24', $('sac24-list').value); alert("Memória gravada."); };
window.abrirModalDevolucaoPendente = () => {
const container = $('lista-dev-pendentes'); container.innerHTML = '';
const pendentes = (window.dbData.transferencias || []).filter(t => t.destino === 'Central BH' && t.status === 'Em Trânsito').reverse();
if(pendentes.length === 0) { container.innerHTML = 'Nenhuma devolução em trânsito.
'; }
else {
pendentes.forEach(p => {
let desc = p.itens ? p.itens.map(it => `${it.qtd}x ${it.item}`).join(', ') : `${p.qtd}x ${p.item}`;
container.innerHTML += `
#${p.id} - ORIGEM: ${p.origem}
${desc}
`;
});
lucide.createIcons();
}
openModal('modal-devolucoes-pendentes');
};
window.selecionarDevolucaoParaAuditoria = (id) => {
const t = window.dbData.transferencias.find(x => x.id === id);
if(!t) return;
window.expectativaItensPendentes = [];
t.itens.forEach(it => {
if (t.seriais && t.seriais[it.item] && t.seriais[it.item].trim() !== "") {
const lista = expandirSeriais(t.seriais[it.item]).filter(s => s && s !== "Nenhum serial informado / Range omitido");
if (lista.length > 0) {
window.expectativaItensPendentes.push({ item: it.item, qtd: lista.length, seriais: t.seriais[it.item] });
}
}
});
$('expectativa-list').value = '';
$('auditoria-origem').value = t.origem;
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('expectativa-list');
atualizarListaPouchExpectativa();
closeModal('modal-devolucoes-pendentes');
window.showToast(`Dados da remessa #${id} importados para conferência.`, 'success');
};
window.abrirModalImportarEnviosConferencia = (showHistory = false) => {
const container = $('lista-envios-importar'); container.innerHTML = '';
const unidsRaw = currentUser.role === 'nucleo' ? (currentUser.unidades || []) : ['Central BH', ...nucleosNames];
const unids = unidsRaw.map(u => (u || '').trim().toLowerCase());
if (currentUser.role === 'spacecom') {
const spaceUnids = (currentUser.unidades || []).map(u => (u || '').trim().toLowerCase());
const moves = (window.dbData.movimentosManuais || []).filter(m => m.tipo === 'Saída para Triagem/Manutenção' && spaceUnids.includes((m.unidade || '').trim().toLowerCase()) && m.status === 'Pendente');
const recibosMap = {};
moves.forEach(m => { if(!recibosMap[m.recibo]) recibosMap[m.recibo] = { id: m.recibo, data: m.data, unidade: m.unidade, itens: [] }; recibosMap[m.recibo].itens.push(`${m.qtd}x ${m.item}`); });
const list = Object.values(recibosMap).reverse();
if(list.length === 0) container.innerHTML = 'Nenhum malote pendente da PPMG.
';
else list.forEach(r => {
container.innerHTML += `
RECIBO PPMG #${r.id} - UNIDADE: ${r.unidade}
${r.itens.join(', ')}
${r.data}
`;
});
lucide.createIcons(); openModal('modal-importar-envios'); return;
}
const isGestor = ['admin', 'central'].includes(String(currentUser?.role || ''));
const unidadesPermitidas = Array.isArray(currentUser?.unidades) ? (currentUser.unidades || []).map(u => (u || '').trim().toLowerCase()) : [];
const podeVerDestino = (destino) => {
if (isGestor) return true;
if (String(currentUser?.role || '') !== 'nucleo') return true;
const destinoLower = (destino || '').trim().toLowerCase();
return unidadesPermitidas.includes(destinoLower);
};
const spacecomReceipts = (window.dbData.movimentosManuais || []).filter(m => {
const destinoMov = m.unidadeDestino || m.unidade || 'N/A';
const statusOk = showHistory ? true : m.status === 'Pendente';
const isSaida = m.tipo === 'Saída Spacecom para Unidade' && !m.arquivado && statusOk && podeVerDestino(destinoMov);
const isReceb = m.tipo === 'Recebimento Spacecom' && !m.arquivado && (showHistory ? true : m.status !== 'Arquivado');
return isSaida || isReceb;
});
if (spacecomReceipts.length > 0) {
container.innerHTML += `Recibos Spacecom
`;
const grouped = {};
spacecomReceipts.forEach(m => {
const key = m.recibo || m.id;
if (!grouped[key]) grouped[key] = { id: key, data: m.data, destino: m.unidadeDestino || m.unidade || 'N/A', itens: [] };
grouped[key].itens.push(`${m.qtd}x ${window.formatItemName(m.item)}`);
});
Object.values(grouped).slice().reverse().forEach(m => {
container.innerHTML += `
RECIBO SPACECOM #${m.id} - DESTINO: ${m.destino}
${m.itens.join(', ')}
${m.data || '-'}
`;
});
lucide.createIcons();
}
let statuses = showHistory ? ['Concluído'] : ['Em Trânsito', 'Recibo', 'Pronto para Envio'];
const list = (window.dbData.transferencias || []).filter(t =>
unids.includes((t.destino || '').trim().toLowerCase()) &&
t.origem === 'Central BH' &&
statuses.includes(t.status) &&
(showHistory ? true : !t.arquivado)
).reverse();
if(list.length === 0 && spacecomReceipts.length === 0) {
container.innerHTML = 'Nenhum malote encontrado.
';
} else {
list.forEach(p => {
let desc = p.itens ? p.itens.map(it => `${it.qtd}x ${it.item}`).join(', ') : `${p.qtd}x ${p.item}`;
let dataFormat = p.data && p.data.includes('-') ? formatDate(p.data) : (p.data || '-');
container.innerHTML += `
#${p.id} - STATUS: ${p.status} - DATA: ${dataFormat}
${desc}
`;
});
lucide.createIcons();
}
openModal('modal-importar-envios');
};
window.importarEnvioParaConferencia = (id, isSpacecomTriage = false) => {
if (isSpacecomTriage) {
const moves = (window.dbData.movimentosManuais || []).filter(m => m.recibo === id);
if(moves.length === 0) return;
itensConfig.forEach(i => { const el = $(`conf-qtd-${i}`); if(el) el.value = '0'; });
const seriais = [];
moves.forEach(m => {
const qInput = $(`conf-qtd-${m.item}`);
if(qInput) qInput.value = m.qtd;
const serialSource = m.seriais || m.serialsBipados || m.obs || '';
if (serialSource) seriais.push(serialSource);
});
$('conf-esperados').value = seriais.filter(Boolean).join('\n');
window.activeConferenciaRecibo = id;
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('conf-esperados');
if(window.verificarNecessidadeSeriaisConf) window.verificarNecessidadeSeriaisConf();
closeModal('modal-importar-envios'); return;
}
const t = window.dbData.transferencias.find(x => x.id === id);
if (t) {
let seriaisExtraidos = [];
if (t.seriais) {
Object.values(t.seriais).forEach(it => {
if (typeof window.expandirSeriais === 'function') {
const seriais = window.expandirSeriais(it).filter(s => s && s !== 'Nenhum serial informado / Range omitido');
seriaisExtraidos.push(...seriais);
}
});
}
if (seriaisExtraidos.length > 0) {
$('conf-esperados').value = seriaisExtraidos.join('\n');
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('conf-esperados');
}
itensConfig.forEach(i => { const el = $(`conf-qtd-${i}`); if(el) el.value = '0'; });
if (t.itens) {
t.itens.forEach(it => { const qInput = $(`conf-qtd-${it.item}`); if(qInput) qInput.value = it.qtd; });
} else {
const qInput = $(`conf-qtd-${t.item}`); if(qInput) qInput.value = t.qtd;
}
window.activeConferenciaTransferId = t.id;
window.activeConferenciaRecibo = null;
if(window.verificarNecessidadeSeriaisConf) window.verificarNecessidadeSeriaisConf();
closeModal('modal-importar-envios'); return;
}
const manualMoves = (window.dbData.movimentosManuais || []).filter(x =>
(x.tipo === 'Saída Spacecom para Unidade' || x.tipo === 'Recebimento Spacecom') &&
(x.recibo === id || x.id === id)
);
if (manualMoves.length > 0) {
itensConfig.forEach(i => { const el = $(`conf-qtd-${i}`); if(el) el.value = '0'; });
let seriaisAgg = [];
manualMoves.forEach(m => {
const qInput = $(`conf-qtd-${m.item}`);
if (qInput) qInput.value = m.qtd;
if (m.seriais || m.serialsBipados) seriaisAgg.push(m.seriais || m.serialsBipados);
});
$('conf-esperados').value = seriaisAgg.join('\n');
$('conf-origem').value = manualMoves[0].origemConferencia || 'Retorno de Manutenção (Limpos)';
renderDestinos();
$('conf-destino').value = manualMoves[0].unidadeDestino || manualMoves[0].unidade || 'Central BH';
window.activeConferenciaTransferId = null;
window.activeConferenciaRecibo = manualMoves[0].recibo || manualMoves[0].id;
if(window.atualizarContagemSeriais) window.atualizarContagemSeriais('conf-esperados');
if(window.verificarNecessidadeSeriaisConf) window.verificarNecessidadeSeriaisConf();
closeModal('modal-importar-envios'); return;
}
};
window.gerarPDFDivergenciaTriagem = () => {
const pares = window.triagemParesConferencia || [];
const faltantes = window.triagemFalhasConferencia || [];
const sobras = window.triagemSobrasConferencia || [];
let bD = [];
if (pares.length > 0) {
pares.forEach(p => bD.push(['PAREAMENTO PROVÁVEL', p.bipado, p.recibo, 'Possível erro de numeração no recibo']));
}
faltantes.forEach(s => bD.push(['FALTANTE', '-', s, 'Consta no recibo, mas não foi bipado']));
sobras.forEach(s => bD.push(['SOBRA', s, '-', 'Bipado, mas não consta no recibo']));
if (bD.length === 0) {
window.showToast('Nenhuma divergência para gerar relatório.', 'warning');
return;
}
window.gerarPdfUniversal({
tipo: 'tabela_relatorio', orientacao: 'portrait',
titulo: 'RELATÓRIO DE INCONSISTÊNCIA DE ESTOQUE - TRIAGEM',
subtitulo: `Data: ${new Date().toLocaleString('pt-BR')} | Responsável: ${currentUser.nome} (${currentUser.masp})`,
head: ['Tipo', 'Leitura Bipada (Físico)', 'Número no Recibo', 'Observação'],
body: bD,
nomeArquivo: `Relatorio_Inconsistencia_Triagem_${new Date().getTime()}.pdf`
});
};
window.executarTriagem = () => {
try {
// Validação segura dos campos
const elPagos = $('sac24-list');
const areaPagos = elPagos ? elPagos.value.trim() : "";
const areaPagosFinal = areaPagos || localStorage.getItem('ppmg_sac24') || "";
let listaFisico = [];
const serialToTypeMap = new Map();
// Verifica se há itens na memória pendente
if (window.auditoriaItensPendentes && window.auditoriaItensPendentes.length > 0) {
window.auditoriaItensPendentes.forEach(it => {
const exp = expandirSeriais(it.seriais).filter(s => s && s !== "Nenhum serial informado / Range omitido");
exp.forEach(s => serialToTypeMap.set(s.replace(/^0+/, '') || '0', it.item));
listaFisico = listaFisico.concat(exp);
});
} else {
// Se não houver itens na memória, puxa direto da caixa de texto
const elRetornados = $('retornados-list');
const areaFisico = elRetornados ? elRetornados.value.trim() : "";
if (areaFisico) {
const exp = expandirSeriais(areaFisico).filter(s => s && s !== "Nenhum serial informado / Range omitido");
const itemVal = $('auditoria-item') ? $('auditoria-item').value : "Desconhecido";
exp.forEach(s => serialToTypeMap.set(s.replace(/^0+/, '') || '0', itemVal));
listaFisico = exp;
}
}
// Bloqueio caso a lista física esteja vazia
if (listaFisico.length === 0) {
alert("Por favor, bipe ou digite os equipamentos recuperados no campo '3. Físico (Triagem)' antes de processar.");
return;
}
let listaExpectativa = [];
if (window.expectativaItensPendentes && window.expectativaItensPendentes.length > 0) {
window.expectativaItensPendentes.forEach(it => {
const exp = expandirSeriais(it.seriais).filter(s => s && s !== "Nenhum serial informado / Range omitido");
listaExpectativa = listaExpectativa.concat(exp);
});
} else {
const elExpectativa = $('expectativa-list');
const areaRecibo = elExpectativa ? elExpectativa.value.trim() : "";
if (areaRecibo) {
listaExpectativa = expandirSeriais(areaRecibo).filter(s => s && s !== "Nenhum serial informado / Range omitido");
}
}
// Mapeamento das listas SAC24 (Indenizados)
const pagasMap = new Map();
if (areaPagosFinal) {
areaPagosFinal.split(/\r?\n/).forEach(l => {
const u = l.trim().toUpperCase();
if (!u) return;
let cols = l.split('\t');
if (cols.length < 7) cols = l.split(/\s{2,}/);
let serial = (cols[1] || "").trim();
if (!/^\d{8,11}$/.test(serial.replace(/^0+/, ''))) {
const sMatch = l.match(/\b([034]\d{8,9})\b/);
serial = sMatch ? sMatch[1] : "";
}
if (!serial) return;
let isPaid = false;
let nfRef = "";
let possibleNF = (cols[7] || "").trim();
if (!/^\d{3,6}$/.test(possibleNF)) {
const idxP = u.indexOf("PERDIDO");
if (idxP !== -1) {
const afterStatus = l.substring(idxP).replace(/\d{2,4}[\/-]\d{2,4}[\/-]\d{2,4}/g, '');
const m = afterStatus.match(/\b(\d{3,6})\b/);
possibleNF = m ? m[1] : "";
}
}
if (u.includes("PERDIDO") && (u.includes("NOTA") || u.includes("COBRAN") || (possibleNF && /^\d{3,6}$/.test(possibleNF)))) {
isPaid = true;
nfRef = possibleNF || "INDENIZADO";
}
if (isPaid) pagasMap.set(serial.replace(/^0+/, '') || '0', nfRef);
});
}
let erarioAbate = [];
let abatesTipos = [];
listaFisico.forEach(s => {
const clean = s.replace(/^0+/, '') || '0';
if(pagasMap.has(clean)) {
const type = serialToTypeMap.get(clean) || "N/A";
erarioAbate.push(`${s} [${type}] (NF: ${pagasMap.get(clean)})`);
abatesTipos.push(type);
}
});
let faltantes = [];
let sobrando = [];
if(listaExpectativa.length > 0) {
const setRecibo = new Set(listaExpectativa.map(s => s.replace(/^0+/, '') || '0'));
const setFisico = new Set(listaFisico.map(s => s.replace(/^0+/, '') || '0'));
listaExpectativa.forEach(s => { if(!setFisico.has(s.replace(/^0+/, '') || '0')) faltantes.push(s); });
listaFisico.forEach(s => { if(!setRecibo.has(s.replace(/^0+/, '') || '0')) sobrando.push(s); });
}
const paresProvaveis = window.calcularPareamentosProvaveisSeriais(sobrando, faltantes, 2);
window.triagemFalhasConferencia = faltantes;
window.triagemSobrasConferencia = sobrando;
window.triagemParesConferencia = paresProvaveis;
// Renderização dos Resultados (Com verificações contra elementos nulos)
const resRoot = $('resultado-triagem');
if (resRoot) resRoot.classList.remove('hidden');
const resDiv = $('resultado-divergencia');
const resEra = $('resultado-erario');
if(listaExpectativa.length > 0 && resDiv) {
resDiv.classList.remove('hidden');
if(faltantes.length === 0 && sobrando.length === 0) {
resDiv.className = "p-6 md:p-8 bg-theme-card border-2 border-green-500 rounded-3xl shadow-2xl";
resDiv.innerHTML = ` Recibo ConferidoTudo certo! O material recebido (${listaFisico.length} un.) confere 100% com o recibo do interior.
`;
} else {
resDiv.className = "p-6 md:p-8 bg-theme-card border-2 border-orange-500 rounded-3xl shadow-2xl";
let html = ` Divergência de Recibo `;
if(faltantes.length > 0) html += `❌ Faltando (Consta no recibo mas não bipado): ${faltantes.length} un. ${faltantes.join(', ')}
`;
if(sobrando.length > 0) html += `⚠️ Sobrando (Bipado mas não consta no recibo): ${sobrando.length} un. ${sobrando.join(', ')}
`;
if(paresProvaveis.length > 0) {
html += `🔎 Possível erro de numeração no recibo:
`;
paresProvaveis.slice(0, 10).forEach(p => {
html += `Bipado: ${p.bipado} | Recibo: ${p.recibo}
`;
});
}
html += ` Baixar Relatório de Inconsistência `;
resDiv.innerHTML = html;
}
} else if (resDiv) {
resDiv.classList.add('hidden');
}
if(erarioAbate.length > 0 && resEra) {
resEra.classList.remove('hidden');
resEra.className = "p-6 md:p-8 bg-theme-card border-2 border-red-500 rounded-3xl shadow-2xl";
resEra.innerHTML = ` Alerta de ErárioCruzamento de dados identificou ${erarioAbate.length} equipamento(s) já indenizados pelo Estado.
${erarioAbate.join('\n')}
`;
window.ultimosAbates = erarioAbate.map(s => s.split(' ')[0]);
window.ultimosDetalhesAbate = erarioAbate;
window.ultimosTiposAbate = abatesTipos;
if($('btn-confirmar-abate')) $('btn-confirmar-abate').classList.remove('hidden');
} else if (resEra) {
resEra.classList.remove('hidden');
resEra.className = "p-6 md:p-8 bg-theme-card border-2 border-green-500 rounded-3xl shadow-2xl";
resEra.innerHTML = ` Sem Alerta de ErárioNenhum dos equipamentos físicos bipados consta na lista de indenizados.
`;
if($('btn-confirmar-abate')) $('btn-confirmar-abate').classList.add('hidden');
}
if($('btn-preparar-spacecom')) $('btn-preparar-spacecom').classList.remove('hidden');
if (window.lucide) lucide.createIcons();
} catch (err) {
console.error("Erro ao processar auditoria:", err);
alert("Ocorreu um erro no cruzamento de dados sistêmicos. Cheque as listas inseridas. Detalhe: " + err.message);
}
};
window.prepararSaidaSpacecomDireto = () => {
const origem = $val('auditoria-origem') || "NÃO INFORMADA";
const pagosSet = new Set((window.ultimosAbates || []).map(s => s.replace(/^0+/, '') || '0'));
let novosItensTriagem = [];
if (window.auditoriaItensPendentes.length > 0) {
window.auditoriaItensPendentes.forEach(it => {
const todosSeriais = expandirSeriais(it.seriais).filter(s => s && s !== "Nenhum serial informado / Range omitido");
const seriaisLimpos = todosSeriais.filter(s => !pagosSet.has(s.replace(/^0+/, '') || '0'));
if (seriaisLimpos.length > 0) {
novosItensTriagem.push({ item: it.item, qtd: seriaisLimpos.length, seriais: seriaisLimpos.join(', ') });
}
});
} else {
const item = $('auditoria-item').value;
const seriais = $('retornados-list').value.trim();
const todosSeriais = expandirSeriais(seriais).filter(s => s && s !== "Nenhum serial informado / Range omitido");
const seriaisLimpos = todosSeriais.filter(s => !pagosSet.has(s.replace(/^0+/, '') || '0'));
if (seriaisLimpos.length > 0) {
novosItensTriagem.push({ item: item, qtd: seriaisLimpos.length, seriais: seriaisLimpos.join(', ') });
}
}
if (novosItensTriagem.length === 0) {
alert("Atenção: Todos os equipamentos bipados constam como 'PAGOS' (Indenizados) na auditoria. Não há itens operacionais válidos para enviar à Spacecom.");
return;
}
window.triagemItensPendentes = novosItensTriagem;
window.switchTab('movimentar');
if($('triagem-item')) $('triagem-item').value = window.triagemItensPendentes[0].item;
if($('triagem-unidade')) $('triagem-unidade').value = 'Central BH';
if($('triagem-origem')) $('triagem-origem').value = `Recolhimento Auditado - ${origem}`;
atualizarListaTriagem();
window.showToast("Dados de auditoria movidos para o Malote Spacecom.", "success");
};
window.executarTriagemOld = () => {
const areaVal = $('sac24-list').value.trim();
const listaOriginal = areaVal !== "" ? areaVal : (localStorage.getItem('ppmg_sac24') || "");
const linhas = listaOriginal.split(/\r?\n/);
const pagasMap = new Map();
linhas.forEach(linha => {
const l = linha.trim(); if (!l) return;
const u = l.toUpperCase();
// Tenta separar por colunas (TAB é o padrão do SAC24)
let cols = l.split('\t');
if (cols.length < 7) cols = l.split(/\s{2,}/); // Fallback para múltiplos espaços
// Localiza o Serial (quase sempre na coluna 2/índice 1)
let serial = (cols[1] || "").trim();
if (!/^\d{8,11}$/.test(serial.replace(/^0+/, ''))) {
// Se a coluna 1 não for o serial, procura em toda a linha um número de 9-10 dígitos
const sMatch = l.match(/\b([034]\d{8,9})\b/);
serial = sMatch ? sMatch[1] : "";
}
if (!serial) return;
let isPaid = false;
let nfRef = "";
let possibleNF = (cols[7] || "").trim();
if (!/^\d{3,6}$/.test(possibleNF)) {
const idxP = u.indexOf("PERDIDO");
if (idxP !== -1) {
const afterStatus = l.substring(idxP).replace(/\d{2,4}[\/-]\d{2,4}[\/-]\d{2,4}/g, '');
const m = afterStatus.match(/\b(\d{3,6})\b/);
possibleNF = m ? m[1] : "";
}
}
if (u.includes("PERDIDO")) {
// Considera pago se houver menção explícita a Nota Fiscal/Cobrança OU se houver um número de NF válido (3-6 dígitos)
if (u.includes("NOTA FISCAL") || u.includes("COBRANÇA") || u.includes("COBRANCA") || (possibleNF && /^\d{3,6}$/.test(possibleNF))) {
isPaid = true;
nfRef = (possibleNF && /^\d{3,6}$/.test(possibleNF)) ? possibleNF : "S/N (Status Indenizado)";
}
}
if (isPaid && serial) {
const cleanS = serial.replace(/^0+/, '') || '0';
pagasMap.set(cleanS, nfRef);
pagasMap.set("0" + cleanS, nfRef); // Mapeia com e sem zero para garantir detecção
}
});
let abate = [];
let detalhesAbate = [];
expandirSeriais($('retornados-list').value).forEach(x => {
if (x && x !== "Nenhum serial informado / Range omitido") {
const cleanX = x.replace(/^0+/, '') || '0';
// Compara o serial bipado contra o banco de dados normalizado
if (pagasMap.has(cleanX)) {
const nfEncontrada = pagasMap.get(cleanX);
abate.push(x);
detalhesAbate.push(`${x} (Confirmado NF: ${nfEncontrada})`);
}
}
});
window.ultimosAbates = abate;
window.ultimosDetalhesAbate = detalhesAbate;
const resTriagem = $('resultado-triagem');
if(abate.length > 0) {
resTriagem.className = "mt-8 p-6 md:p-8 bg-theme-card border-2 border-red-500 rounded-3xl shadow-2xl";
resTriagem.innerHTML = ` Alerta de ErárioO cruzamento de dados bateu e o sistema levantou a "bandeira vermelha" (Alerta de Erário), avisando que você recuperou fisicamente um equipamento que o Estado já havia pago para a Spacecom como "Perdido".O pagamento foi confirmado através da Nota Fiscal de Número identificada na planilha SAC24, conforme detalhado na lista abaixo:
${detalhesAbate.join('\n')}
Confirmar Abate e Baixar PDF `;
} else {
resTriagem.className = "mt-8 p-6 md:p-8 bg-theme-card border-2 border-green-500 rounded-3xl shadow-2xl";
resTriagem.innerHTML = ` Tudo LimpoNenhum serial físico consta na lista de equipamentos indenizados/perdidos.
Limpo. Sem registros para abater.
`;
}
lucide.createIcons(); resTriagem.classList.remove('hidden');
};
window.limparAuditoria = () => {
if(confirm("Deseja limpar as listas e o resultado da auditoria?")) {
$('sac24-list').value = '';
$('retornados-list').value = '';
$('expectativa-list').value = '';
$('auditoria-origem').value = '';
if($('auditoria-item')) $('auditoria-item').selectedIndex = 0;
if($('expectativa-item')) $('expectativa-item').selectedIndex = 0;
window.auditoriaItensPendentes = [];
window.expectativaItensPendentes = [];
atualizarListaPouchAuditoria();
atualizarListaPouchExpectativa();
const resTriagem = $('resultado-triagem');
if(resTriagem) resTriagem.classList.add('hidden');
const resDiv = $('resultado-divergencia');
if(resDiv) { resDiv.innerHTML = ''; resDiv.classList.add('hidden'); }
const resEra = $('resultado-erario');
if(resEra) { resEra.innerHTML = ''; resEra.classList.add('hidden'); }
if($('btn-confirmar-abate')) $('btn-confirmar-abate').classList.add('hidden');
if($('btn-preparar-spacecom')) $('btn-preparar-spacecom').classList.add('hidden');
if(window.atualizarContagemSeriais) { window.atualizarContagemSeriais('sac24-list'); window.atualizarContagemSeriais('retornados-list'); window.atualizarContagemSeriais('expectativa-list'); }
}
};
window.salvarEGerarPDFAuditoria = () => {
if(!window.ultimosAbates || window.ultimosAbates.length === 0) { alert("Nenhum abate."); return; }
const origemRecuperacao = $val('auditoria-origem') || "NÃO INFORMADA";
const seq = (window.dbData.auditorias ? window.dbData.auditorias.length : 0) + 1;
const seqStr = seq.toString().padStart(2, '0');
const displayRecibo = "RECIBO DE EQUIPAMENTOS PAGOS " + seqStr;
const listaSeriais = window.ultimosDetalhesAbate || window.ultimosAbates;
const itemTipoResumo = [...new Set(window.ultimosTiposAbate)].join(', ');
const novoAbate = { id: "AUDIT-" + Date.now().toString().slice(-4), data: new Date().toLocaleString('pt-BR'), responsavel: currentUser.nome, masp: currentUser.masp, seriais: listaSeriais, qtd: window.ultimosAbates.length, origem: origemRecuperacao, item: itemTipoResumo, seq: seq };
if(!window.dbData.auditorias) window.dbData.auditorias = []; window.dbData.auditorias.push(novoAbate); saveToCloud(['auditorias']);
const groupedItems = {};
window.ultimosDetalhesAbate.forEach((detail, idx) => {
const type = window.ultimosTiposAbate[idx] || "Equipamento";
if (!groupedItems[type]) groupedItems[type] = { item: type, qtd: 0, details: [] };
groupedItems[type].qtd++;
groupedItems[type].details.push(detail.replace(/\[.*?\]/, '').trim());
});
const itensConfigPdf = Object.values(groupedItems).map(it => ({ item: it.item, qtd: it.qtd, seriais: it.details, cols: 2, fs: 8 }));
const obs = prompt("Deseja inserir alguma observação neste documento de auditoria? (Deixe em branco se não)");
window.gerarPdfUniversal({
tipoLayout: 'TRIAGEM', orientacao: 'portrait', isDevolucao: false, origem: origemRecuperacao, numeroRecibo: displayRecibo, tipoRecibo: "", observacoes: obs,
itens: itensConfigPdf, nomeRepassador: currentUser.nome, maspRepassador: currentUser.masp, lotacaoRepassador: currentUser.role === 'nucleo' ? (currentUser.unidades||[])[0] : 'PPMG-CD/DME',
cidade: "Belo Horizonte", nomeArquivo: `Guia_Oficial_Auditoria_${seqStr}.pdf`
});
alert("Sucesso! Histórico atualizado na Nuvem e PDF gerado.");
window.ultimosAbates = []; window.ultimosDetalhesAbate = [];
$('sac24-list').value = ''; $('retornados-list').value = ''; $('expectativa-list').value = ''; $('auditoria-origem').value = '';
if(window.atualizarContagemSeriais) {
window.atualizarContagemSeriais('sac24-list');
window.atualizarContagemSeriais('retornados-list');
window.atualizarContagemSeriais('expectativa-list');
}
$('resultado-triagem').classList.add('hidden');
window.auditoriaItensPendentes = []; atualizarListaPouchAuditoria();
window.expectativaItensPendentes = []; atualizarListaPouchExpectativa();
};
window.normalizarUnidadeAcesso = function(unidade) {
const txt = String(unidade || '').trim();
if (!txt) return '';
const up = txt
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toUpperCase()
.replace(/\s+/g, ' ')
.trim();
if (up === 'SUBNUCLEO DE SAO JOAO DEL REY' || up === 'SUBNUCLEO DE SAO JOAO DEL REI' || up === 'SUBNUCLEO SAO JOAO DEL REY' || up === 'SUBNUCLEO SAO JOAO DEL REI' || up === 'SJDR' || up === 'SUBNUCLEO SJDR') {
return 'Subnúcleo SJDR';
}
return txt;
};
window.obterUnidadesAcessoGestao = function() {
const base = new Set(['Central BH', ...(nucleosNames || [])]);
(window.dbData.users || []).forEach(u => {
(u.unidades || []).forEach(un => {
const txt = window.normalizarUnidadeAcesso(un);
if (!txt || txt === 'TODOS') return;
base.add(txt);
});
});
if (base.has('Subnúcleo de São João Del Rey')) base.delete('Subnúcleo de São João Del Rey');
if (base.has('Subnúcleo de São João del Rey')) base.delete('Subnúcleo de São João del Rey');
base.add('Subnúcleo SJDR');
return Array.from(base);
};
window.usuarioTemAcessoUnidade = function(user, unidade) {
if (!user || !unidade) return false;
if (user.role === 'admin') return true;
const unidadeNorm = window.normalizarUnidadeAcesso(unidade);
if (user.role === 'central') return unidadeNorm === 'Central BH';
const unidades = (Array.isArray(user.unidades) ? user.unidades : []).map(window.normalizarUnidadeAcesso);
if (unidades.includes('TODOS')) return true;
return unidades.includes(unidadeNorm);
};
window.renderResumoAcessosGestao = function() {
const tb = $('users-table-body');
if (!tb) return;
const bloco = $('admin-users-access-summary');
if (!bloco) return;
const users = window.dbData.users || [];
const policiaisLocais = users.filter(u => u.role === 'nucleo');
const acessosAdmin = users.filter(u => u.role === 'admin').length;
const acessosCentral = users.filter(u => u.role === 'central').length;
const unidades = window.obterUnidadesAcessoGestao().filter(un => un !== 'Central BH');
const filtroAnterior = $('admin-acesso-unidade-filtro')?.value || 'TODAS';
const unidadeFiltro = unidades.includes(filtroAnterior) || filtroAnterior === 'TODAS' ? filtroAnterior : 'TODAS';
const contagemPorUnidade = unidades.map(un => {
const qtd = policiaisLocais.filter(u => window.usuarioTemAcessoUnidade(u, un)).length;
return { unidade: un, qtd };
});
const unidadesSemPolicialLocal = contagemPorUnidade.filter(x => x.qtd === 0);
const listaNominal = (unidadeFiltro === 'TODAS'
? users
: users.filter(u => (u.role === 'admin') || window.usuarioTemAcessoUnidade(u, unidadeFiltro))
).slice().sort((a, b) => {
const nomeA = String(a.nome || '').localeCompare(String(b.nome || ''), 'pt-BR');
if (nomeA !== 0) return nomeA;
return String(a.masp || '').localeCompare(String(b.masp || ''), 'pt-BR');
});
const linhas = (unidadeFiltro === 'TODAS'
? contagemPorUnidade
: contagemPorUnidade.filter(x => x.unidade === unidadeFiltro)
).sort((a, b) => {
if (a.qtd === 0 && b.qtd > 0) return -1;
if (a.qtd > 0 && b.qtd === 0) return 1;
return a.unidade.localeCompare(b.unidade, 'pt-BR');
});
const totalPoliciaisLocaisFiltro = unidadeFiltro === 'TODAS'
? policiaisLocais.length
: policiaisLocais.filter(u => window.usuarioTemAcessoUnidade(u, unidadeFiltro)).length;
const opcoes = ['TODAS', ...unidades]
.map(un => `${un === 'TODAS' ? 'TODAS AS UNIDADES' : un} `)
.join('');
const linhasHtml = linhas.length
? linhas.map(x => {
const semLocal = x.qtd === 0;
const classeQtd = semLocal ? 'text-red-500' : 'text-gold-500';
const badge = semLocal
? 'Sem policial local '
: 'Coberta ';
return `${x.unidade} ${x.qtd} ${badge} `;
}).join('')
: 'Sem dados de acesso para o filtro selecionado. ';
const listaSemLocal = unidadesSemPolicialLocal.length
? unidadesSemPolicialLocal.map(x => `${x.unidade} `).join(' ')
: 'Todas as unidades com policial local cadastrado ';
const listaNominalHtml = listaNominal.length
? listaNominal.map(u => {
const perfis = u.role === 'admin'
? 'ADMINISTRADOR GERAL'
: (u.role === 'central' ? 'OPERADOR CENTRAL BH' : (u.role === 'spacecom' ? 'SPACECOM' : 'NÚCLEO REGIONAL'));
const lotacao = Array.isArray(u.unidades) ? u.unidades.join(', ') : (u.unidades || '');
return `${u.nome || 'N/A'} ${u.masp || '-'} ${perfis}
${lotacao || 'Sem lotação informada'}
`;
}).join('')
: 'Nenhum acesso localizado para o filtro selecionado.
';
bloco.innerHTML = `Filtro de Unidade ${opcoes}
Policiais Locais Cadastrados
${totalPoliciaisLocaisFiltro}
Unidades Sem Policial Local
${unidadesSemPolicialLocal.length}
Acessos Central BH
${acessosCentral}
Acessos Admin
${acessosAdmin}
Unidades sem policiais locais cadastrados
${listaSemLocal}
Lista nominal de acessos
${unidadeFiltro === 'TODAS' ? 'Todas as unidades' : unidadeFiltro}
${listaNominal.length} registro(s) ${listaNominalHtml}
Unidade Qtd. Policiais Locais Situação ${linhasHtml}
`;
$('admin-acesso-unidade-filtro')?.addEventListener('change', () => window.renderResumoAcessosGestao());
};
window.renderAdminUsers = function() {
const tb = $('users-table-body'); if(!tb) return; tb.innerHTML = ''; if (!window.dbData.users) return;
window.renderResumoAcessosGestao();
window.dbData.users.forEach((u, i) => {
let badge = u.role === 'admin' ? 'bg-red-900/20 text-red-500 border border-red-500/30' : (u.role === 'central' ? 'bg-blue-900/20 text-blue-500 border border-blue-500/30' : (u.role === 'spacecom' ? 'bg-purple-900/20 text-purple-500 border border-purple-500/30' : 'bg-theme-body text-theme-muted border border-theme-border'));
let rLabel = u.role === 'admin' ? 'Admin ' : (u.role === 'central' ? 'Central BH ' : (u.role === 'spacecom' ? 'Spacecom ' : 'Núcleo '));
let unids = (u.unidades || []).join(', ');
tb.innerHTML += `${u.nome}${u.email} ${u.masp}${u.cpf} ${rLabel} ${unids} `;
});
const card = tb.closest('.card-panel');
if (card) {
let bottom = $('admin-users-nominal-bottom');
if (!bottom) {
bottom = document.createElement('div');
bottom.id = 'admin-users-nominal-bottom';
bottom.className = 'border-t border-theme-border bg-theme-card/70 px-5 py-4';
card.appendChild(bottom);
}
const listagem = (window.dbData.users || []).slice().sort((a, b) => String(a.nome || '').localeCompare(String(b.nome || ''), 'pt-BR'));
const itens = listagem.map(u => {
const lot = (Array.isArray(u.unidades) ? u.unidades.map(window.normalizarUnidadeAcesso) : []).join(', ');
const perfil = u.role === 'admin' ? 'ADMIN' : (u.role === 'central' ? 'CENTRAL' : (u.role === 'spacecom' ? 'SPACECOM' : 'NÚCLEO'));
return `${u.nome || 'N/A'} ${u.masp || '-'}
${perfil} | ${lot || 'Sem lotação'}
`;
}).join('');
bottom.innerHTML = `Lista nominal de servidores com acesso
${listagem.length} registro(s) `;
}
lucide.createIcons();
window.syncAdminUsersScrollBar?.();
const scroller = $('admin-users-scroll-area');
if (scroller && !scroller.dataset.scrollSyncBound) {
scroller.dataset.scrollSyncBound = '1';
scroller.addEventListener('scroll', () => window.syncAdminUsersScrollBar?.(), { passive: true });
}
}
window.syncAdminUsersScrollBar = function() {
const scroller = $('admin-users-scroll-area');
const track = $('admin-users-scroll-track');
const thumb = $('admin-users-scroll-thumb');
if (!scroller || !track || !thumb) return;
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
const trackHeight = track.clientHeight;
if (maxScrollTop <= 0 || trackHeight <= 0) {
thumb.style.opacity = '0';
thumb.style.height = '100%';
thumb.style.transform = 'translate(-50%, 0)';
return;
}
const thumbHeight = Math.max(42, Math.round((scroller.clientHeight / scroller.scrollHeight) * trackHeight));
const thumbTop = Math.round((scroller.scrollTop / maxScrollTop) * (trackHeight - thumbHeight));
thumb.style.opacity = '1';
thumb.style.height = `${thumbHeight}px`;
thumb.style.transform = `translate(-50%, ${thumbTop}px)`;
};
window.addEventListener('resize', () => window.syncAdminUsersScrollBar?.());
window.toggleUnidadeSelect = function() {
const role = $('usr-role').value;
const uDiv = $('div-usr-unidades');
const inputMasp = $('usr-masp');
const labelMasp = inputMasp ? inputMasp.previousElementSibling : null;
if(uDiv) uDiv.style.display = (role === 'nucleo' || role === 'spacecom') ? 'block' : 'none';
if(labelMasp) labelMasp.innerText = (role === 'spacecom') ? 'ID / Código (Opcional)' : 'MASP (Login Oficial)';
if(inputMasp) inputMasp.required = (role !== 'spacecom');
};
window.editUser = function(i) {
editingUserIndex = i; const u = window.dbData.users[i]; $('usr-nome').value = u.nome; $('usr-cpf').value = u.cpf; $('usr-masp').value = u.masp || ''; $('usr-senha').value = ''; $('usr-senha').placeholder = 'Deixe em branco para manter a atual'; $('usr-senha').required = false; $('usr-email').value = u.email; $('usr-role').value = u.role; window.toggleUnidadeSelect();
const setUser = new Set((u.unidades || []).map(window.normalizarUnidadeAcesso));
document.querySelectorAll('.chk-unidade').forEach(c => c.checked = setUser.has(window.normalizarUnidadeAcesso(c.value)));
$('btn-cancel-edit').classList.remove('hidden'); $('btn-submit-user').innerHTML = "Atualizar Conta"; $('form-user-title').innerHTML = " Editando: " + u.nome; window.scrollTo({top: 0, behavior: 'smooth'}); lucide.createIcons();
};
window.cancelEditUser = function() {
editingUserIndex = -1; $('form-user').reset(); $('usr-senha').placeholder = 'Defina a senha inicial'; $('usr-senha').required = true; window.toggleUnidadeSelect(); $('btn-cancel-edit').classList.add('hidden'); $('btn-submit-user').innerHTML = "Salvar Autorização na Nuvem"; $('form-user-title').innerHTML = "Novo Servidor";
};
window.deleteUser = function(i) {
if(window.dbData.users[i].masp === currentUser.masp) { alert("Ação Intercetada: Não pode excluir o seu próprio login."); return; } if(confirm("Confirma a Exclusão Definitiva deste servidor da base da PPMG?")){ window.dbData.users.splice(i, 1); saveToCloud(['users']).then(() => { renderAdminUsers(); }); }
};
$('form-user')?.addEventListener('submit', (e) => {
e.preventDefault(); const m = cleanMasp($('usr-masp').value); const r = $('usr-role').value; let un = [];
if (r !== 'spacecom' && !m) { alert("O campo MASP é obrigatório para perfis da PPMG."); return; } // This line is already correct
if (r === 'admin') un = ['TODOS'];
else if (r === 'central') un = ['Central BH'];
else {
document.querySelectorAll('.chk-unidade:checked').forEach(c => un.push(window.normalizarUnidadeAcesso(c.value)));
un = Array.from(new Set(un));
if (un.length === 0) { alert("Selecione pelo menos uma Lotação."); return; }
}
let senhaSalvar = $val('usr-senha'); if (editingUserIndex > -1) { if (senhaSalvar === "") { senhaSalvar = window.dbData.users[editingUserIndex].senha; } } else { if (senhaSalvar === "") { alert("Defina uma senha inicial."); return; } }
const obj = { nome: $val('usr-nome'), cpf: $val('usr-cpf'), email: $val('usr-email'), masp: m, senha: senhaSalvar, role: r, unidades: un };
if (editingUserIndex > -1) { const exists = window.dbData.users.findIndex(us => cleanMasp(us.masp) === m); if (exists !== -1 && exists !== editingUserIndex) { alert("Este MASP já existe."); return; } window.dbData.users[editingUserIndex] = obj; } else { const exists = window.dbData.users.findIndex(us => cleanMasp(us.masp) === m); if (exists !== -1) { alert("Matrícula já cadastrada."); return; } window.dbData.users.push(obj); }
saveToCloud(['users']).then(() => { showToast("Servidor salvo com sucesso!", "success"); cancelEditUser(); renderAdminUsers(); });
});
window.gerarRelatorio = () => {
const tipo = $('rel-tipo').value; const u = $('rel-unidade').value; const s = $val('rel-data-inicio'); const e = $val('rel-data-fim'); const p = $val('rel-policial').toLowerCase();
const thead = $('relatorio-thead'); const tb = $('relatorio-tbody'); if(!tb || !thead) return; tb.innerHTML = '';
const unidadesPermitidasRel = Array.isArray(currentUser.unidades)
? currentUser.unidades
: (typeof currentUser.unidades === 'string' ? currentUser.unidades.split(',').map(x => x.trim()).filter(Boolean) : []);
const isGestao = currentUser.role === 'admin';
const unidadeSelecionada = isGestao ? u : (u || unidadesPermitidasRel[0] || 'Central BH');
let buffer = '';
if (tipo === 'ESTOQUE') {
thead.innerHTML = `Pólo/Unidade Equipamento Físico Atual Meta Estabelecida Classificação `;
let unidsToCheck = [];
if (unidadeSelecionada === 'TODOS') unidsToCheck = isGestao ? ['Central BH', ...nucleosNames] : (unidadesPermitidasRel.length ? unidadesPermitidasRel : ['Central BH']);
else unidsToCheck = [unidadeSelecionada];
let hasData = false;
unidsToCheck.forEach(un => {
itensConfig.forEach(it => {
let dataObj = null; if (un === 'Central BH') dataObj = window.dbData.inventory[it]; else if (window.dbData.nucleosInventory[un]) dataObj = window.dbData.nucleosInventory[un][it];
if (dataObj) {
hasData = true; const status = getStatusInfo(dataObj.atual, dataObj.ideal, dataObj.baixo, un);
buffer += `${un} ${window.formatItemName(it)} ${dataObj.atual} ${dataObj.ideal} ${status.label} `;
}
});
});
if(!hasData) tb.innerHTML = 'Sem parâmetros lançados no inventário. ';
else tb.innerHTML = buffer;
lucide.createIcons(); return;
}
if (tipo === 'USO_TZ_UNIDADE') {
thead.innerHTML = `Data Unidade Qtd TZPR Usada Policial Servidor Lançador `;
const unidadeFiltro = unidadeSelecionada;
let registros = window.obterAdmissoesVisiveis().filter(ad => {
const iso = dataRegistroParaISO(ad.data);
if (!iso) return false;
const passStart = !s || iso >= s;
const passEnd = !e || iso <= e;
const passUnid = (unidadeFiltro === 'TODOS') || ad.setor === unidadeFiltro;
const qtdTz = Array.isArray(ad.itens) ? ad.itens.filter(it => it.item === 'TZPR').reduce((acc, it) => acc + (parseInt(it.qtd, 10) || 0), 0) : 0;
if (qtdTz <= 0) return false;
if (!p) return passStart && passEnd && passUnid;
const txt = `${ad.nome || ''} ${ad.masp || ''} ${ad.setor || ''} ${ad.registradoPor || ''}`.toLowerCase();
return passStart && passEnd && passUnid && txt.includes(p);
});
if (registros.length === 0) {
tb.innerHTML = 'Sem uso de TZPR para os filtros selecionados. ';
return;
}
registros.sort((a, b) => {
const da = dataRegistroParaISO(a.data) || '';
const db = dataRegistroParaISO(b.data) || '';
return db.localeCompare(da);
});
let totalTz = 0;
registros.forEach(ad => {
const qtdTz = ad.itens.filter(it => it.item === 'TZPR').reduce((acc, it) => acc + (parseInt(it.qtd, 10) || 0), 0);
totalTz += qtdTz;
buffer += `${(ad.data || '').split(' ')[0] || '-'} ${ad.setor || '-'} ${qtdTz} ${ad.nome || '-'}MASP: ${ad.masp || '-'} ${ad.registradoPor || '-'} `;
});
buffer += `Total de TZPR no período: ${totalTz} `;
tb.innerHTML = buffer;
lucide.createIcons();
return;
}
if (tipo === 'FLUXO') {
thead.innerHTML = `Unidade Regional Ativos SAC24 Meta 12% (Ideal) Estoque Atual (TZPR) Classificação Ação Sugerida `;
Object.keys(window.enderecosNucleos).forEach(n => {
if(n === 'Central BH' || n === 'Extensão Gameleira') return;
if(unidadeSelecionada !== 'TODOS' && n !== unidadeSelecionada) return;
let inv = window.dbData.nucleosInventory[n] || {};
let ativos = (inv.ativosTZPR || 0) + (inv.ativosUPR || 0); let ideal = Math.ceil(ativos * 0.12); let atual = inv['TZPR']?.atual || 0;
let ratio = ideal > 0 ? atual / ideal : (atual > 0 ? 2 : 0); let status = 'Sem Dados'; let acao = '-'; let cor = 'text-theme-muted';
if (ativos > 0) { if (ratio >= 1.5) { status = 'Superávit'; acao = `Recolher ${atual - ideal}`; cor = 'text-blue-500'; } else if (ratio >= 1.0) { status = 'Saudável'; acao = 'Manter'; cor = 'text-green-500'; } else if (ratio >= 0.5) { status = 'Atenção'; acao = `Enviar ${ideal - atual}`; cor = 'text-orange-500'; } else { status = 'Crítico'; acao = `Enviar ${ideal - atual}`; cor = 'text-red-500'; } }
buffer += `${n} ${ativos} ${ideal} ${atual} ${status} ${acao} `;
});
if(buffer === '') tb.innerHTML = 'Sem resultados. ';
else tb.innerHTML = buffer;
lucide.createIcons(); return;
}
if (tipo === 'AUDITORIAS') {
thead.innerHTML = `Data/Hora Auditor Responsável Origem Relatada Qtd Seriais Lista de Seriais Pagos Encontrados `;
let audits = window.dbData.auditorias || [];
if(s) audits = audits.filter(a => new Date(a.data.split(' ')[0].split('/').reverse().join('-')) >= new Date(s));
if(e) audits = audits.filter(a => new Date(a.data.split(' ')[0].split('/').reverse().join('-')) <= new Date(e));
if(p) audits = audits.filter(a => a.responsavel.toLowerCase().includes(p) || a.masp.includes(p) || (a.origem || '').toLowerCase().includes(p));
if(audits.length === 0) { tb.innerHTML = 'Nenhum registro de auditoria no período. '; return; }
audits.forEach(a => { buffer += `${a.data} ${a.responsavel}${a.masp} ${a.origem || 'NÃO INFORMADA'} ${a.qtd} ${(a.seriais||[]).join(', ')} `; });
tb.innerHTML = buffer; lucide.createIcons(); return;
}
thead.innerHTML = `Data/Hora Log Natureza Logística Item Volume Origem -> Destino Status Auditoria Servidor Traço / Anotação `;
let masterData = [];
(window.dbData.transferencias||[]).forEach(tr => {
let op = tr.origem === 'Central BH' ? 'Suprimento' : (tr.destino === 'Central BH' ? 'Devolução BH' : 'Transf. Lateral');
let resp = tr.aceitePor ? `Receção: ${tr.aceitePor}` : (tr.transportador ? `Motorista: ${tr.transportador.nome}` : 'Aguardando Atuação');
let obs = tr.transportador ? `VTR/Lotação Transp: ${tr.transportador.lotacao}` : (tr.entregador ? `Guiché: Entregue por ${tr.entregador.nome} (${tr.entregador.lotacao})` : 'Sistema');
if(tr.itens) {
tr.itens.forEach(it => { masterData.push({ _rawDate: tr.data, id: tr.id, dataStr: formatDate(tr.data), operacao: op, item: it.item, qtd: it.qtd, origem: tr.origem, destino: tr.destino, status: tr.status, resp: resp, obs: obs }); });
} else {
masterData.push({ _rawDate: tr.data, id: tr.id, dataStr: formatDate(tr.data), operacao: op, item: tr.item, qtd: tr.qtd, origem: tr.origem, destino: tr.destino, status: tr.status, resp: resp, obs: obs });
}
});
(window.dbData.admissoes||[]).forEach(ad => {
if(!ad.data) return; let dp = ad.data.split(' ')[0].split('/'); let dt = dp.length === 3 ? `${dp[2]}-${dp[1]}-${dp[0]}` : ad.data;
if(ad.itens) {
ad.itens.forEach(it => { masterData.push({ _rawDate: dt, id: ad.id, dataStr: ad.data.split(' ')[0], operacao: 'Uso na Ponta (Admissão)', item: it.item, qtd: it.qtd, origem: ad.setor, destino: 'Rua / Policial', status: 'Concluído', resp: `${ad.nome} (${ad.masp})`, obs: `Lançado Ponto-a-Ponto por: ${ad.registradoPor}` }); });
} else {
masterData.push({ _rawDate: dt, id: ad.id, dataStr: ad.data.split(' ')[0], operacao: 'Uso na Ponta (Admissão)', item: ad.item, qtd: ad.qtd, origem: ad.setor, destino: 'Rua / Policial', status: 'Concluído', resp: `${ad.nome} (${ad.masp})`, obs: `Lançado Ponto-a-Ponto por: ${ad.registradoPor}` });
}
});
(window.dbData.movimentosManuais||[]).forEach(mv => {
if(!mv.data) return; let dp = mv.data.split(' ')[0].split('/'); let dt = dp.length === 3 ? `${dp[2]}-${dp[1]}-${dp[0]}` : mv.data;
masterData.push({ _rawDate: dt, id: mv.id, dataStr: mv.data.split(' ')[0], operacao: `Sede: ${mv.tipo}`, item: mv.item, qtd: mv.qtd, origem: 'Cofre Central', destino: 'Cofre Central', status: 'Concluído', resp: mv.responsavel, obs: mv.obs });
});
masterData.sort((a,b) => { if (a._rawDate > b._rawDate) return -1; if (a._rawDate < b._rawDate) return 1; return 0; });
let result = masterData.filter(tr => {
let passUnid = (unidadeSelecionada === 'TODOS') || (tr.origem === unidadeSelecionada || tr.destino === unidadeSelecionada);
let passStart = !s || (tr._rawDate >= s);
let passEnd = !e || (tr._rawDate <= e);
let passType = true;
if (tipo === 'ENTRADA_FORNECEDOR') passType = tr.operacao.includes('Entrada de Novos');
else if (tipo === 'RETORNO_TRIAGEM') passType = tr.operacao.includes('Retorno de Manutenção');
else if (tipo === 'TRANSFERENCIAS') passType = tr.operacao.includes('Suprimento');
else if (tipo === 'TRANSF_LATERAL') passType = tr.operacao.includes('Transf. Lateral');
else if (tipo === 'DEVOLUCOES') passType = tr.operacao.includes('Devolução BH');
else if (tipo === 'USO') passType = tr.operacao.includes('Uso na Ponta');
else if (tipo === 'TRIAGEM') passType = tr.operacao.includes('Saída para Triagem');
else if (tipo === 'AJUSTES') passType = tr.operacao.includes('Ajuste');
let passSearch = true;
if (p !== '') {
const allText = Object.values(tr).join(' ').toLowerCase();
passSearch = allText.includes(p);
}
return passUnid && passStart && passEnd && passType && passSearch;
});
if (result.length === 0) { tb.innerHTML = 'Nenhum evento corresponde à pesquisa no banco de dados da Nuvem PPMG. '; return; }
const maxR = 200;
const displayList = result.slice(0, maxR);
displayList.forEach(tr => {
let sBadge = '';
let pdfBtnRel = '';
if(tr.status === 'Pendente') sBadge = 'Pendente ';
else if (tr.status === 'Em Trânsito') sBadge = 'Em Transporte ';
else sBadge = 'Finalizado ';
if (tr.id && (tr.id.startsWith('DME') || tr.id.startsWith('DEV'))) {
pdfBtnRel = ` `;
}
let cColor = 'text-theme-muted';
if(tr.operacao.includes('Uso') || tr.operacao.includes('Saída')) cColor = 'text-red-500';
else if(tr.operacao.includes('Devolução') || tr.operacao.includes('Retorno')) cColor = 'text-orange-500';
else if(tr.operacao.includes('Transf')) cColor = 'text-purple-500';
else if(tr.operacao.includes('Suprimento') || tr.operacao.includes('Entrada')) cColor = 'text-blue-500';
buffer += `#${tr.id}${tr.dataStr} ${tr.operacao} ${window.formatItemName(tr.item)} ${tr.qtd} ${tr.origem} ${tr.destino} ${sBadge}${pdfBtnRel} ${tr.resp} ${tr.obs} `;
});
tb.innerHTML = buffer;
if (result.length > maxR) {
tb.innerHTML += `⚠️ Mostrando apenas os últimos ${maxR} de ${result.length} registros. Use filtros ou exporte para ver o histórico completo. `;
}
lucide.createIcons();
};
window.exportarRelatorioXLS = () => {
const tb = $("relatorio-tbody");
if(tb.rows.length === 0 || (tb.rows.length === 1 && tb.rows[0].innerText.includes("Selecione"))) { alert("Gere o relatório na tela primeiro para exportar a planilha."); return; }
const thead = $("relatorio-thead");
const headers = [];
if (thead && thead.rows.length > 0) {
const hcells = thead.rows[0].cells;
for (let i = 0; i < hcells.length; i++) {
headers.push((hcells[i].innerText || `COL_${i+1}`).replace(/(\r\n|\n|\r)/gm, ' ').trim());
}
}
const rows = [];
for(let i=0; i < tb.rows.length; i++) {
const row = tb.rows[i];
const obj = {};
for(let j=0; j < row.cells.length; j++) {
const key = headers[j] || `COL_${j+1}`;
obj[key] = row.cells[j].innerText.replace(/(\r\n|\n|\r)/gm, ' | ').trim();
}
rows.push(obj);
}
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Relatorio');
XLSX.writeFile(wb, `RelatorioLogistico_SIGME_PPMG_${new Date().toISOString().slice(0,10).replace(/-/g,'')}.xlsx`);
};
window.exportarRelatorio = window.exportarRelatorioXLS;
window.gerarPDFRelatorio = async () => {
const tb = $("relatorio-tbody");
if(tb.rows.length === 0 || (tb.rows.length === 1 && tb.rows[0].innerText.includes("Selecione"))) { alert("Gere a tabela na tela primeiro para autorizar a exportação do PDF Oficial."); return; }
const thead = $("relatorio-thead");
let headers = [];
const ths = thead.getElementsByTagName("th");
for(let i=0; i {
if(e) e.stopPropagation();
$('notification-dropdown').classList.toggle('hidden');
};
document.addEventListener('click', (e) => {
const b = $('notification-bell'), nd = $('notification-dropdown');
if(b && !b.contains(e.target) && nd && !nd.classList.contains('hidden')) nd.classList.add('hidden');
const tBtn = $('theme-dropdown-container'), tDrop = $('theme-dropdown');
if(tBtn && !tBtn.contains(e.target) && tDrop && !tDrop.classList.contains('hidden')) tDrop.classList.add('hidden');
if(!e.target.closest('.card-actions-dropdown') && !e.target.closest('button[onclick*="toggleCardDropdown"]')) window.closeAllCardDropdowns();
});
window.goToNotification = (tId) => {
window.switchTab(tId);
$('notification-dropdown')?.classList.add('hidden');
};
function updateNotifications() {
if (!currentUser) return;
let nts = [];
const b = $('notification-bell'), i = $('bell-icon'), bdg = $('notification-count'), lst = $('notification-list');
if (!b || !lst) return;
if (currentUser.role === 'admin' || currentUser.role === 'central') {
(window.dbData.transferencias||[]).filter(t => t.status === 'Pendente' && t.origem === 'Central BH').reverse().forEach(p => {
let desc = p.itens ? p.itens.map(it => `${it.qtd}x ${it.item}`).join(', ') : `${p.qtd}x ${p.item}`;
nts.push({ text: `Requisição de ${p.destino}: ${desc}`, tab: 'logistica', type: 'sup' });
});
if (currentUser.role === 'admin') {
(window.dbData.transferencias||[]).filter(t => t.status === 'Pendente' && t.origem !== 'Central BH').reverse().forEach(p => { nts.push({ text: `Analisar transf. de ${p.origem} para ${p.destino}`, tab: 'logistica', type: 'transf' }); });
}
(window.dbData.transferencias||[]).filter(t => t.status === 'Em Trânsito' && t.destino === 'Central BH').reverse().forEach(d => {
nts.push({ text: `Malote físico a caminho de ${d.origem}`, tab: 'devolucoes', type: 'dev' });
});
} else {
const unidadesLower = (currentUser.unidades||[]).map(u => (u || '').trim().toLowerCase());
(window.dbData.transferencias||[]).filter(t => t.status === 'Em Trânsito' && unidadesLower.includes((t.destino || '').trim().toLowerCase())).reverse().forEach(t => {
let desc = t.itens ? t.itens.map(it => `${it.qtd}x ${it.item}`).join(', ') : `${t.qtd}x ${t.item}`;
nts.push({ text: `Carga na estrada para a sua base: ${desc}`, tab: 'logistica', type: 'rec' });
});
(window.dbData.transferencias||[]).filter(t => (t.status === 'Autorizado' || t.status === 'Em Preparação') && unidadesLower.includes((t.destino || '').trim().toLowerCase())).reverse().forEach(a => {
let desc = a.itens ? a.itens.map(it => `${it.qtd}x ${it.item}`).join(', ') : `${a.qtd}x ${a.item}`;
nts.push({ text: `Logística aprovou envio de ${desc}`, tab: 'logistica', type: 'apr' });
});
}
lst.innerHTML = '';
if (nts.length > 0) {
b.classList.remove('hidden');
bdg.innerText = nts.length > 99 ? '99+' : nts.length;
bdg.classList.remove('hidden');
i.classList.add('ringing', 'text-gold-500'); i.setAttribute('data-lucide', 'bell-ring');
i.classList.remove('text-theme-muted');
nts.forEach(n => {
let icH = n.type === 'sup' || n.type === 'rec' ? ' ' : (n.type === 'dev' ? ' ' : ' ');
lst.innerHTML += ``;
});
} else {
bdg.classList.add('hidden');
i.classList.remove('ringing', 'text-gold-500'); i.setAttribute('data-lucide', 'bell');
i.classList.add('text-theme-muted');
lst.innerHTML = 'Painel Limpo. Sem Alertas Logísticos Ativos.
';
}
lucide.createIcons();
}
window.salvarLimites = () => {
const n = $('lim-nucleo').value, i = $('lim-item').value, idl = parseInt($('lim-ideal').value,10), b = parseInt($('lim-baixo').value,10);
if(isNaN(idl) || isNaN(b)){ alert("As metas exigem dados numéricos absolutos para gravar."); return; }
if(n === 'Central BH'){
window.dbData.inventory[i].ideal = idl; window.dbData.inventory[i].baixo = b;
} else {
if(!window.dbData.nucleosInventory[n]) window.dbData.nucleosInventory[n] = {};
if(!window.dbData.nucleosInventory[n][i]) window.dbData.nucleosInventory[n][i] = {atual:0};
window.dbData.nucleosInventory[n][i].ideal = idl; window.dbData.nucleosInventory[n][i].baixo = b;
}
saveToCloud(['inventory', 'nucleosInventory']).then(() => {
closeModal('modal-limites'); showToast("Metas de Alertas Atualizadas!", "success");
renderDashboard(); renderNucleoDashboard();
});
};
/* DASHBOARD ANALYTICS */
Chart.defaults.color = '#9ca3af';
Chart.defaults.font.family = 'Barlow';
window.renderGraficosAnalytics = () => {
const sel = document.getElementById('grafico-filtro-nucleo');
if(sel.options.length === 0) {
sel.innerHTML = 'Comparativo de Todos (Ranking) ';
sel.innerHTML += 'Estoque Central BH ';
nucleosNames.forEach(n => sel.innerHTML += `${n} `);
}
renderGraficoNucleo();
};
window.renderGraficoNucleo = () => {
if (window.graficosAtivos.chartNucleos) window.graficosAtivos.chartNucleos.destroy();
if (window.graficosAtivos.chartGeral) window.graficosAtivos.chartGeral.destroy();
const sel = document.getElementById('grafico-filtro-nucleo').value;
const ctxNucleos = document.getElementById('chartNucleos').getContext('2d');
const ctxGeral = document.getElementById('chartGeral').getContext('2d');
// Gráfico Doughnut (Distribuição por Item)
let labelsGeral = []; let dataGeral = [];
if (sel === 'TODOS') {
itensConfig.forEach(i => {
let total = (window.dbData.inventory[i]?.atual || 0);
nucleosNames.forEach(n => { if(window.dbData.nucleosInventory[n]?.[i]) total += window.dbData.nucleosInventory[n][i].atual; });
if(total > 0) { labelsGeral.push(window.formatItemName(i)); dataGeral.push(total); }
});
} else {
itensConfig.forEach(i => {
let val = (sel === 'Central BH' ? window.dbData.inventory[i]?.atual : window.dbData.nucleosInventory[sel]?.[i]?.atual) || 0;
if(val > 0) { labelsGeral.push(window.formatItemName(i)); dataGeral.push(val); }
});
}
window.graficosAtivos.chartGeral = new Chart(ctxGeral, {
type: 'doughnut',
data: { labels: labelsGeral, datasets: [{ data: dataGeral, backgroundColor: ['#D4AF37', '#DA251D', '#3b82f6', '#8b5cf6', '#10b981', '#f97316'], borderColor: '#111827', borderWidth: 2 }] },
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ffffff', font: { size: 10, weight: 'bold' } } } } }
});
// Gráfico de Barras (Ranking ou Detalhes)
let labels = []; let data = []; let bgColors = [];
if(sel === 'TODOS') {
nucleosNames.forEach(n => {
let total = 0; itensConfig.forEach(i => { if(window.dbData.nucleosInventory[n]?.[i]) total += window.dbData.nucleosInventory[n][i].atual; });
if(total > 0) { labels.push(n.replace('Núcleo ', 'N. ').replace('Subnúcleo ', 'S. ')); data.push(total); bgColors.push('#D4AF37'); }
});
} else {
itensConfig.forEach((i, idx) => {
let val = (sel === 'Central BH' ? window.dbData.inventory[i]?.atual : window.dbData.nucleosInventory[sel]?.[i]?.atual) || 0;
labels.push(i); data.push(val);
const colors = ['#D4AF37', '#DA251D', '#3b82f6', '#8b5cf6', '#10b981', '#f97316'];
bgColors.push(colors[idx % colors.length]);
});
}
window.graficosAtivos.chartNucleos = new Chart(ctxNucleos, {
type: 'bar',
data: { labels: labels, datasets: [{ label: 'Qtd de Peças', data: data, backgroundColor: bgColors, borderRadius: 4 }] },
options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, grid: { color: '#374151' }, ticks: { color: '#9ca3af' } }, x: { grid: { display: false }, ticks: { color: '#9ca3af', font: { size: 9 } } } }, plugins: { legend: { display: false } } }
});
};
window.obterDadosPlantao = () => {
const dt = $('plantao-data')?.value; const un = $('plantao-unidade')?.value;
if(!dt || !un) { alert("Selecione a data do plantão e a unidade."); return null; }
const targetDateStr = dt.replace(/-/g, '');
const isDateInRange = (dStr) => {
if(!dStr) return false;
let datePart = dStr.split(' ')[0].replace(/,/g, '');
if(datePart.includes('/')) { const p = datePart.split('/'); if(p.length === 3) return `${p[2]}${p[1]}${p[0]}` === targetDateStr; }
if(datePart.includes('-')) { return datePart.replace(/-/g, '') === targetDateStr; }
return false;
};
const dataBr = dt.split('-').reverse().join('/'); let eventos = [];
(window.dbData.transferencias || []).forEach(tr => {
const itensDesc = tr.itens ? tr.itens.map(i=>`${i.qtd}x ${i.item}`).join(', ') : `${tr.qtd}x ${tr.item}`;
const srs = tr.seriais ? Object.values(tr.seriais).join(' | ') : '-';
const dataFormatada = tr.data.includes('-') ? formatDate(tr.data) : tr.data;
if(isDateInRange(tr.data)) {
if(tr.origem === un) {
eventos.push({ hora: dataFormatada, op: `Remessa/Devolução Iniciada`, itens: itensDesc, seriais: srs, obs: `Malote #${tr.id} registrado para envio à unidade: ${tr.destino}.` });
}
if(tr.destino === un) {
eventos.push({ hora: dataFormatada, op: `Solicitação de Suprimento`, itens: itensDesc, seriais: srs, obs: `Pedido #${tr.id} gerado no sistema. Origem: ${tr.origem}.` });
}
}
if(tr.transportador && tr.transportador.dataHora && isDateInRange(tr.transportador.dataHora)) {
if(tr.origem === un || tr.destino === un) {
const tipoRota = tr.origem === un ? 'Saída de Material' : 'Carga em Trânsito para Unidade';
eventos.push({ hora: tr.transportador.dataHora, op: tipoRota, itens: itensDesc, seriais: srs, obs: `Despachado por: ${tr.transportador.nome}. VTR: ${tr.transportador.lotacao}` });
}
}
if(tr.aceiteData && isDateInRange(tr.aceiteData) && tr.destino === un) {
eventos.push({ hora: tr.aceiteData, op: `Recebimento de Material (Concluído)`, itens: itensDesc, seriais: srs, obs: `Material integrado ao estoque local por: ${tr.aceitePor}` });
}
});
(window.dbData.admissoes || []).forEach(ad => {
if(isDateInRange(ad.data) && ad.setor === un) {
let itensDesc = ad.itens ? ad.itens.map(i=>`${i.qtd}x ${i.item}`).join(', ') : `${ad.qtd}x ${ad.item}`;
let srs = ad.seriais ? Object.values(ad.seriais).join(' | ') : '-';
eventos.push({ hora: ad.data, op: `Saída na Ponta (Acautelamento Policial)`, itens: itensDesc, seriais: srs, obs: `Policial: ${ad.nome} (MASP: ${ad.masp}) - Lançado por: ${ad.registradoPor}` });
}
});
(window.dbData.movimentosManuais || []).forEach(mv => {
if(isDateInRange(mv.data)) {
let matchUnit = false;
if (un === 'Central BH') { if (mv.obs.includes('Central BH') || mv.tipo.includes('Entrada Operacional') || mv.tipo.includes('Saída para Triagem') || mv.tipo.includes('Ajuste')) matchUnit = true; }
else { if (mv.obs.includes(un) || mv.responsavel === currentUser.nome) matchUnit = true; }
if(matchUnit) {
const seriaisMv = String(mv.seriais || mv.serialsBipados || mv.seriaisConferenciaUnidade || '-');
eventos.push({ hora: mv.data, op: mv.tipo, itens: `${mv.qtd}x ${mv.item}`, seriais: seriaisMv, obs: `Anotação: ${mv.obs} (Resp: ${mv.responsavel})` });
}
}
});
if (un === 'Central BH') {
(window.dbData.auditorias || []).forEach(au => {
if(isDateInRange(au.data)) { eventos.push({ hora: au.data, op: `Auditoria / Abate Sistêmico`, itens: `${au.qtd} equipamentos`, seriais: (au.seriais||[]).join(', '), obs: `Auditor Governamental: ${au.responsavel}` }); }
});
}
eventos.sort((a,b) => {
let pA = a.hora.split(' ')[0].split('/'); let pB = b.hora.split(' ')[0].split('/');
let tA = a.hora.split(' ')[1] || '00:00:00'; let tB = b.hora.split(' ')[1] || '00:00:00';
return `${pA[2]||pA[0]}-${pA[1]}-${pA[0]} ${tA}`.localeCompare(`${pB[2]||pB[0]}-${pB[1]}-${pB[0]} ${tB}`);
}); return { eventos, dataBr, un };
};
window.previaPlantao = () => {
const dados = window.obterDadosPlantao(); if(!dados) return; const tb = $('plantao-tbody'); tb.innerHTML = '';
if(dados.eventos.length === 0) { tb.innerHTML = `Nenhuma movimentação registrada nesta data e unidade. `; return; }
dados.eventos.forEach(ev => { tb.innerHTML += `${ev.hora} ${ev.op} ${ev.itens} ${ev.seriais} ${ev.obs} `; });
};
window.gerarPDFPlantao = () => {
const dados = window.obterDadosPlantao(); if(!dados) return;
if(dados.eventos.length === 0) { alert("Não existem movimentações para gerar a via PDF."); return; }
const inv = dados.un === 'Central BH' ? window.dbData.inventory : (window.dbData.nucleosInventory[dados.un] || {});
const getQ = (k) => (inv[k] && typeof inv[k].atual !== 'undefined') ? inv[k].atual : 0;
const textoEstoque = `Durante o plantão, foi realizado o acompanhamento do estoque logístico do ${dados.un}, mantendo-se monitoramento contínuo dos materiais operacionais utilizados pela Diretoria de Gestão e Monitoramento Eletrônico.\n` +
`Permanecendo o estoque com o seguinte quantitativo:\n` +
`* TZPR: ${getQ('TZPR')} unidades;\n* UPR: ${getQ('UPR')} unidades;\n* CM: ${getQ('CM')} unidades;\n` +
`* TRAVAS: ${getQ('TRAVAS')} unidades;\n* CINTAS: ${getQ('CINTAS')} unidades;\n* FONTES: ${getQ('FONTE')} unidades;\n` +
`Durante o período analisado, foram realizadas as movimentações logísticas, conferências de materias e acompanhamento sistêmico de entradas e saídas no SIGME/DME, sem alterações extraordinárias além das registradas em sistem.\n` +
`Período Analisado: ${dados.dataBr} | Extraído por: ${currentUser.nome} (${currentUser.masp}) em ${new Date().toLocaleString('pt-BR')}`;
let bD = [];
dados.eventos.forEach(ev => {
const listaS = expandirSeriais(ev.seriais).filter(s => s !== "-" && s !== "Nenhum serial informado / Range omitido");
if (listaS.length > 50) {
for (let i = 0; i < listaS.length; i += 50) {
const chunk = listaS.slice(i, i + 50).join(', ');
bD.push([
i === 0 ? ev.hora : "",
i === 0 ? ev.op : ev.op + " (Cont.)",
i === 0 ? ev.itens : "",
chunk,
i === 0 ? ev.obs : ""
]);
}
} else {
bD.push([ev.hora, ev.op, ev.itens, ev.seriais, ev.obs]);
}
});
window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'landscape', titulo: `RELATÓRIO / PASSAGEM DE PLANTÃO LOGÍSTICO - ${dados.un.toUpperCase()}`, subtitulo: textoEstoque, head: ['Data/Hora', 'Natureza da Operação', 'Equipamentos e Quantidade', 'Relação de Seriais Óticos/Anotados', 'Servidor Responsável / Observações'], body: bD, nomeArquivo: `Passagem_Plantao_${dados.un.replace(/\s/g,'_')}.pdf` });
};
/* INTERCEPTADOR INTELIGENTE DE LEITOR ÓTICO (BIPADOR) */
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.target.tagName.toLowerCase() === 'textarea' && e.target.classList.contains('font-mono')) {
e.preventDefault();
const cursor = e.target.selectionStart;
const txtAntes = e.target.value.substring(0, cursor);
const txtDepois = e.target.value.substring(cursor);
const match = txtAntes.match(/([A-Z0-9]+)(\s*)$/i);
if (match) {
const recemBipado = match[1].toUpperCase();
const indexToCut = txtAntes.length - match[0].length;
const textoSemUltimo = txtAntes.substring(0, indexToCut);
const listaAnterior = expandirSeriais(textoSemUltimo);
const listaAnteriorNormalizada = listaAnterior.map(s => s.replace(/^0+/, '') || '0');
const recemBipadoNormalizado = recemBipado.replace(/^0+/, '') || '0';
if (listaAnteriorNormalizada.includes(recemBipadoNormalizado) && !recemBipado.includes("NENHUM")) {
alert(`🚨 AVISO DE DUPLICIDADE 🚨\n\nO equipamento de serial [ ${recemBipado} ] já foi bipado nesta caixa!\n\nO sistema o removeu automaticamente para manter a integridade da contagem.`);
e.target.value = textoSemUltimo + txtDepois;
e.target.selectionStart = e.target.selectionEnd = textoSemUltimo.length;
e.target.dispatchEvent(new Event('input'));
return;
}
}
if (txtAntes.trim().length > 0 && !txtAntes.endsWith(', ')) {
const separador = txtAntes.endsWith(',') ? ' ' : ', ';
e.target.value = txtAntes + separador + txtDepois;
e.target.selectionStart = e.target.selectionEnd = cursor + separador.length;
e.target.dispatchEvent(new Event('input'));
}
}
});
const checkIOSStandalone = () => {
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
const isStandalone = window.navigator.standalone || window.matchMedia('(display-mode: standalone)').matches;
if (isIOS && isStandalone) {
const fsBtn = document.getElementById('btn-fullscreen');
if (fsBtn) { fsBtn.style.display = 'none'; fsBtn.classList.add('!hidden'); }
}
};
checkIOSStandalone();
window.handleFileUpload = async (event, targetId) => {
const file = event.target.files[0];
if (!file) return;
const targetEl = $(targetId);
if (!targetEl) return;
const fileName = file.name.toLowerCase();
targetEl.value = "Processando leitura do arquivo. Por favor, aguarde...";
try {
let extractedText = "";
if (fileName.endsWith('.pdf')) {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
extractedText += textContent.items.map(item => item.str).join(" ") + "\n";
}
} else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls') || fileName.endsWith('.csv')) {
const arrayBuffer = await file.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
workbook.SheetNames.forEach(sheetName => { const sheet = workbook.Sheets[sheetName]; extractedText += XLSX.utils.sheet_to_csv(sheet) + "\n"; });
} else if (fileName.endsWith('.docx')) {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer });
extractedText = result.value;
} else if (fileName.endsWith('.txt')) {
extractedText = await file.text();
} else {
alert("Formato não suportado. Utilize PDF, DOCX, XLSX, XLS, CSV ou TXT."); targetEl.value = ""; event.target.value = ""; return;
}
let currentVal = targetEl.value;
if (currentVal.includes("Processando leitura")) currentVal = "";
targetEl.value = currentVal ? currentVal + "\n" + extractedText : extractedText;
if (window.atualizarContagemSeriais) window.atualizarContagemSeriais(targetId);
window.showToast("Arquivo importado e lido com sucesso!", "success"); lucide.createIcons();
} catch (error) { console.error(error); alert("Erro ao ler o arquivo: " + error.message); targetEl.value = ""; }
event.target.value = "";
};
window.handleSac24FileUpload = async (event) => {
const file = event.target.files[0]; if (!file) return;
const statusDiv = $('sac24-parse-status');
statusDiv.className = 'text-center text-xs font-bold uppercase tracking-widest mt-3 text-blue-400 animate-pulse';
statusDiv.innerHTML = ' Lendo arquivo...';
lucide.createIcons();
try {
let extractedText = ""; const fileName = file.name.toLowerCase();
if (fileName.endsWith('.pdf')) {
const arrayBuffer = await file.arrayBuffer(); const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); extractedText += textContent.items.map(item => item.str).join(" ") + "\n"; }
} else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls') || fileName.endsWith('.csv')) {
const arrayBuffer = await file.arrayBuffer(); const workbook = XLSX.read(arrayBuffer, { type: 'array' });
workbook.SheetNames.forEach(sheetName => { const sheet = workbook.Sheets[sheetName]; extractedText += XLSX.utils.sheet_to_csv(sheet, { header: 1 }).replace(/,/g, ' ') + "\n"; });
} else if (fileName.endsWith('.docx')) {
const arrayBuffer = await file.arrayBuffer(); const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer }); extractedText = result.value;
} else if (fileName.endsWith('.txt')) {
extractedText = await file.text();
} else { throw new Error("Formato de arquivo não suportado."); }
window.processarTabelaTexto(extractedText, statusDiv);
} catch (error) { console.error("Erro ao processar arquivo SAC24:", error); statusDiv.className = 'text-center text-xs font-bold uppercase tracking-widest mt-3 text-red-500'; statusDiv.innerText = 'Falha ao ler o arquivo.'; }
finally { event.target.value = ""; }
};
window.processarTabelaColada = () => {
const text = $('sac24-paste-area').value;
if (!text.trim()) { alert("Cole a tabela na área de texto primeiro!"); return; }
const statusDiv = $('sac24-parse-status');
window.processarTabelaTexto(text, statusDiv);
};
window.processarTabelaTexto = (extractedText, statusDiv) => {
try {
const linhas = extractedText.toUpperCase().split('\n'); let encontrados = 0;
const blockRegex = /(?:[\d.]+)\s*-\s*(?:[\d.]+)\s*-\s*(?:[\d.]+)/g;
let totais = {};
const listaCompleta = ['Central BH', ...nucleosNames];
listaCompleta.forEach(n => { totais[n] = { aTZ: 0, aUPR: 0, eTZ: 0, eUPR: 0, iTZ: 0, iUPR: 0, tTZ: 0, tUPR: 0, found: false }; });
for (let linha of linhas) {
if (linha.includes('SUB-TOTAL') || linha.includes('SUBTOTAL') || linha.startsWith('TOTAL')) continue;
const matches = [...linha.matchAll(blockRegex)];
if (matches && matches.length >= 7) {
let b0 = matches[0][0].split('-'); let b1 = matches[1][0].split('-'); let b5 = matches[5][0].split('-'); let b6 = matches[6][0].split('-');
let aTZ = parseInt(b0[0].replace(/\./g, '').trim(), 10) || 0; let aUPR = parseInt(b0[1].replace(/\./g, '').trim(), 10) || 0;
let tTZ = parseInt(b1[0].replace(/\./g, '').trim(), 10) || 0; let tUPR = parseInt(b1[1].replace(/\./g, '').trim(), 10) || 0;
let eTZ = parseInt(b5[0].replace(/\./g, '').trim(), 10) || 0; let eUPR = parseInt(b5[1].replace(/\./g, '').trim(), 10) || 0;
let iTZ = parseInt(b6[0].replace(/\./g, '').trim(), 10) || 0; let iUPR = parseInt(b6[1].replace(/\./g, '').trim(), 10) || 0;
let targetNucleo = 'Central BH';
for (let nucleo of nucleosNames) {
if (nucleo === 'Central BH') continue;
let city = nucleo.toUpperCase().replace(/SUBNÚCLEO/g, "").replace(/NÚCLEO/g, "").replace(/EXTENSÃO/g, "").trim();
if (city.startsWith("DE ")) city = city.substring(3).trim();
let aliases = [city];
if (city === 'SJDR' || city === 'SÃO JOÃO DEL REY' || city === 'SÃO JOÃO DEL REI') aliases.push('SJDR', 'SÃO JOÃO DEL REI', 'SAO JOAO DEL REI', 'SÃO JOÃO DEL REY', 'SAO JOAO DEL REY');
if (city === 'ALMENARA') aliases.push('ALMEN' + 'AR');
if (city === 'GOV. VALADARES') aliases.push('GOVERNADOR VALADARES', 'VALADARES');
if (aliases.some(alias => linha.includes(alias))) {
targetNucleo = nucleo; break;
}
}
totais[targetNucleo].aTZ += aTZ; totais[targetNucleo].aUPR += aUPR;
totais[targetNucleo].tTZ += tTZ; totais[targetNucleo].tUPR += tUPR;
totais[targetNucleo].eTZ += eTZ; totais[targetNucleo].eUPR += eUPR;
totais[targetNucleo].iTZ += iTZ; totais[targetNucleo].iUPR += iUPR;
totais[targetNucleo].found = true;
}
}
listaCompleta.forEach(nucleo => {
let t = totais[nucleo];
if (t.found) {
if ($(`atz-${nucleo}`)) $(`atz-${nucleo}`).value = t.aTZ;
if ($(`etz-${nucleo}`)) $(`etz-${nucleo}`).value = t.eTZ;
if ($(`itz-${nucleo}`)) $(`itz-${nucleo}`).value = t.iTZ;
if ($(`ttz-${nucleo}`)) $(`ttz-${nucleo}`).value = t.tTZ;
if ($(`aupr-${nucleo}`)) $(`aupr-${nucleo}`).value = t.aUPR;
if ($(`iupr-${nucleo}`)) $(`iupr-${nucleo}`).value = t.iUPR;
if ($(`eupr-${nucleo}`)) $(`eupr-${nucleo}`).value = t.eUPR;
if ($(`tupr-${nucleo}`)) $(`tupr-${nucleo}`).value = t.tUPR;
encontrados++;
}
});
if (statusDiv) {
statusDiv.classList.remove('animate-pulse');
if (encontrados > 0) { statusDiv.className = 'text-center text-xs font-bold uppercase tracking-widest mt-3 text-green-500'; statusDiv.innerText = `${encontrados} polos extraídos com sucesso!`; window.showToast('Dados importados! Salve para concluir.', 'success'); const b = $('btn-salvar-metas'); if(b) { b.classList.add('alert-pulse', '!border-green-500', '!text-white'); b.innerHTML = ' Confirmar Sincronização'; setTimeout(() => { b.classList.remove('alert-pulse', '!border-green-500', '!text-white'); b.innerHTML = 'Sincronizar Metas'; }, 5000); if(window.lucide) lucide.createIcons(); } }
else { statusDiv.className = 'text-center text-xs font-bold uppercase tracking-widest mt-3 text-orange-500'; statusDiv.innerText = 'Nenhuma unidade compatível encontrada no arquivo.'; window.showToast('Não foi possível extrair dados.', 'error'); }
}
} catch (error) { console.error(error); if (statusDiv) { statusDiv.className = 'text-center text-xs font-bold uppercase tracking-widest mt-3 text-red-500'; statusDiv.innerText = 'Falha ao processar os dados.'; } }
};
let geojsonMG = null;
let mapaMinas = null;
window.renderFluxoEstoque = () => {
if (!mapaMinas) {
mapaMinas = L.map('mapa-minas').setView([-18.5122, -44.5550], 6);
mapaMinas.createPane('estadoPane');
mapaMinas.getPane('estadoPane').style.zIndex = 250;
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabel/{z}/{x}/{y}{r}.png', { attribution: '© CartoDB', maxZoom: 19 }).addTo(mapaMinas);
fetch('https://servicodados.ibge.gov.br/api/v3/malhas/estados/31?formato=application/vnd.geo+json&resolucao=5')
.then(r => r.json())
.then(data => {
geojsonMG = L.geoJSON(data, { pane: 'estadoPane', style: { color: '#000000', weight: 0.8, fillColor: '#e5e7eb', fillOpacity: 0.9 } }).addTo(mapaMinas);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png', { attribution: '© CartoDB', maxZoom: 19, pane: 'shadowPane', bounds: geojsonMG.getBounds() }).addTo(mapaMinas);
if (window.atualizarCorMapaDeCalor) window.atualizarCorMapaDeCalor();
}).catch(e => console.log('Erro ao carregar malha IBGE:', e));
}
mapaMinas.invalidateSize();
mapaMinas.eachLayer((layer) => { if (layer instanceof L.Marker || layer instanceof L.Circle) { mapaMinas.removeLayer(layer); } });
if (window.heatLayer) mapaMinas.removeLayer(window.heatLayer);
window.hasAtivosGlobal = false;
let heatData = [];
Object.keys(window.enderecosNucleos).forEach(n => {
const info = window.enderecosNucleos[n];
if(!info) return;
let isCentral = (n === 'Central BH');
let isGameleira = (n === 'Extensão Gameleira');
let isNucleo = n.startsWith('Núcleo');
let aTZ, iTZ, aUPR, iUPR, tTZ, eTZ, idealAjustado, atualFisico;
if (isCentral) {
aTZ = window.dbData.inventory.ativosTZPR || 0; iTZ = window.dbData.inventory.inspecaoTZPR || 0;
aUPR = window.dbData.inventory.ativosUPR || 0; iUPR = window.dbData.inventory.inspecaoUPR || 0;
tTZ = window.dbData.inventory.transitoTZPR || 0; eTZ = window.dbData.inventory.estoqueSacTZPR || 0;
idealAjustado = window.dbData.inventory['TZPR']?.ideal || 0; atualFisico = window.dbData.inventory['TZPR']?.atual || 0;
} else {
aTZ = window.dbData.nucleosInventory[n]?.ativosTZPR || 0; iTZ = window.dbData.nucleosInventory[n]?.inspecaoTZPR || 0;
aUPR = window.dbData.nucleosInventory[n]?.ativosUPR || 0; iUPR = window.dbData.nucleosInventory[n]?.inspecaoUPR || 0;
tTZ = window.dbData.nucleosInventory[n]?.transitoTZPR || 0; eTZ = window.dbData.nucleosInventory[n]?.estoqueSacTZPR || 0;
idealAjustado = window.dbData.nucleosInventory[n]?.['TZPR']?.ideal || 0; atualFisico = window.dbData.nucleosInventory[n]?.['TZPR']?.atual || 0;
}
let pinColor = '#9ca3af';
let status = 'Sem Dados';
let actionTxt = 'Aguardando Inserção de Ativos';
if (n === "Admissão/NSI") {
pinColor = '#10b981';
status = 'Saudável';
actionTxt = 'Setor de Admissão (Fluxo Diário)';
} else if (aTZ > 0 || aUPR > 0) {
window.hasAtivosGlobal = true;
let ratio = idealAjustado > 0 ? atualFisico / idealAjustado : (atualFisico > 0 ? 2 : 0);
if (ratio >= 1.5) { pinColor = '#3b82f6'; status = 'Superávit'; actionTxt = `Recolher ${atualFisico - idealAjustado} un.`; }
else if (ratio >= 1.0) { pinColor = '#10b981'; status = 'Saudável'; actionTxt = `Estoque Ideal`; }
else if (ratio >= 0.5) { pinColor = '#f59e0b'; status = 'Atenção'; actionTxt = `Enviar ${idealAjustado - atualFisico} un.`; }
else { pinColor = '#ef4444'; status = 'Crítico'; actionTxt = `Enviar ${idealAjustado - atualFisico} un. URGENTE`; }
L.circle([info.lat, info.lng], { color: pinColor, weight: 2, fillColor: pinColor, fillOpacity: 0.5, radius: 30000 }).addTo(mapaMinas);
heatData.push([info.lat, info.lng, Math.min(1.0, (aTZ + aUPR) / 500)]);
} else {
if (isNucleo) { pinColor = '#000000'; status = 'Núcleo (Sem Dados)'; }
else { pinColor = '#000000'; status = 'Subnúcleo (Sem Dados)'; }
}
let iconHtml = '';
let customIcon = null;
let astColor = (aTZ > 0 || aUPR > 0) ? pinColor : '#000000';
let fSize = '30px';
let mt = '-10px';
iconHtml = `*
`;
customIcon = L.divIcon({ className: 'custom-pin-container', html: iconHtml, iconSize: [30, 30], iconAnchor: [15, 15], popupAnchor: [0, -10] });
const marker = L.marker([info.lat, info.lng], { icon: customIcon }).addTo(mapaMinas);
let tooltipContent = `${n} 📍 ${info.end} 📞 ${info.tel} ✉️ ${info.email}
`;
marker.bindTooltip(tooltipContent, { className: 'custom-tooltip-map bg-white/90 text-black', direction: 'top', offset: [0, -25], opacity: 0.95 });
let appTransitoTZPR = 0;
if (!isCentral && !isGameleira) {
(window.dbData.transferencias || []).forEach(tr => {
// Considera trânsito apenas o que saiu da central (Em Trânsito ou Recibo)
if (tr.destino === n && (tr.status === 'Em Trânsito' || tr.status === 'Recibo')) {
if (tr.itens) { tr.itens.forEach(it => { if (it.item === 'TZPR') appTransitoTZPR += parseInt(it.qtd, 10); }); }
else if (tr.item === 'TZPR') { appTransitoTZPR += parseInt(tr.qtd, 10); }
}
});
}
let transitoDisplay = `${tTZ} `;
if (isCentral) {
transitoDisplay = `${tTZ} `;
} else if (tTZ !== appTransitoTZPR && (tTZ > 0 || appTransitoTZPR > 0)) {
transitoDisplay = `${tTZ} SAC / ${appTransitoTZPR} SIGME `;
}
let popupContent = `${n} `;
if (aTZ > 0 && atualFisico !== eTZ) {
popupContent += `
⚠️ Divergência Físico: SIGME (${atualFisico}) vs SAC (${eTZ})
`;
}
popupContent += `
Ativos TZPR: ${aTZ} Ativos UPR: ${aUPR} Inspeção: ${iTZ} Trânsito: ${transitoDisplay}
`;
let popupDivMsg = (aTZ > 0 && atualFisico !== eTZ) ? `
⚠️ SAC Diz: ${eTZ} ` : '';
if (isGameleira) {
popupContent += `
Sede Administrativa e Gestão Diretoria Operacional (DME)STATUS: ESTOQUE LOGÍSTICO
`;
} else {
let txtSede = isCentral ? `Sede Administrativa e Gestão Diretoria Operacional (DME) ` : ``;
popupContent += `${txtSede}Ativos (TZPR/UPR): ${aTZ} / ${aUPR} Meta Ajustada: ${idealAjustado} Estoque Físico: ${atualFisico} ${popupDivMsg}STATUS: ${status.toUpperCase()}
`;
}
marker.bindPopup(popupContent, { className: 'custom-popup-light' });
});
if (heatData.length > 0 && typeof L.heatLayer === 'function') {
window.heatLayer = L.heatLayer(heatData, {radius: 120, blur: 90, maxZoom: 8, max: 1.0, gradient: {0.2: '#3b82f6', 0.5: '#10b981', 0.8: '#f59e0b', 1.0: '#ef4444'}}).addTo(mapaMinas);
}
if (window.atualizarCorMapaDeCalor) window.atualizarCorMapaDeCalor();
setTimeout(() => { if (mapaMinas) mapaMinas.invalidateSize(); if (window.lucide) lucide.createIcons(); }, 500);
};
window.atualizarCorMapaDeCalor = () => {
if(geojsonMG) {
geojsonMG.setStyle({ color: '#000000', weight: 0.8, fillColor: '#e5e7eb', fillOpacity: 0.9 });
}
};
window.abrirModalAtivos = () => {
const grid = $('grid-ativos-inputs'); grid.innerHTML = '';
const listaCompleta = ['Central BH', ...nucleosNames];
listaCompleta.forEach(n => {
let inv = n === 'Central BH' ? window.dbData.inventory : window.dbData.nucleosInventory[n];
let aTZ = inv?.ativosTZPR || 0; let iTZ = inv?.inspecaoTZPR || 0;
let aUPR = inv?.ativosUPR || 0; let iUPR = inv?.inspecaoUPR || 0;
let tTZ = inv?.transitoTZPR || 0; let eTZ = inv?.estoqueSacTZPR || 0;
let tUPR = inv?.transitoUPR || 0; let eUPR = inv?.estoqueSacUPR || 0;
let sigmeAtual = inv?.['TZPR']?.atual || 0;
let divColor = (aTZ > 0 && sigmeAtual !== eTZ) ? 'text-red-400 border-red-400 focus:border-red-500' : 'text-white border-theme-border';
grid.innerHTML += `
${n} ${aTZ > 0 && sigmeAtual !== eTZ ? `
Físico SIGME: ${sigmeAtual} un.
` : ''}
`;
}); openModal('modal-ativos');
};
$('form-ativos')?.addEventListener('submit', (e) => {
e.preventDefault();
const listaCompleta = ['Central BH', ...nucleosNames];
listaCompleta.forEach(n => {
let invTarget = n === 'Central BH' ? window.dbData.inventory : window.dbData.nucleosInventory[n];
if(!invTarget) {
if (n !== 'Central BH') { window.dbData.nucleosInventory[n] = {}; invTarget = window.dbData.nucleosInventory[n]; }
else return;
}
const aTZ = parseInt($(`atz-${n}`).value, 10) || 0; const eTZ = parseInt($(`etz-${n}`).value, 10) || 0;
const iTZ = parseInt($(`itz-${n}`).value, 10) || 0; const tTZ = parseInt($(`ttz-${n}`).value, 10) || 0;
const aUPR = parseInt($(`aupr-${n}`) ? $(`aupr-${n}`).value : invTarget.ativosUPR, 10) || 0;
const iUPR = parseInt($(`iupr-${n}`) ? $(`iupr-${n}`).value : invTarget.inspecaoUPR, 10) || 0;
const eUPR = parseInt($(`eupr-${n}`) ? $(`eupr-${n}`).value : invTarget.estoqueSacUPR, 10) || 0;
const tUPR = parseInt($(`tupr-${n}`) ? $(`tupr-${n}`).value : invTarget.transitoUPR, 10) || 0;
invTarget.ativosTZPR = aTZ; invTarget.estoqueSacTZPR = eTZ;
invTarget.inspecaoTZPR = iTZ; invTarget.transitoTZPR = tTZ;
invTarget.ativosUPR = aUPR; invTarget.inspecaoUPR = iUPR;
invTarget.estoqueSacUPR = eUPR; invTarget.transitoUPR = tUPR;
let metaTZPR = Math.max(0, Math.ceil(aTZ * 0.12) - iTZ - tTZ);
let metaUPR = Math.max(0, Math.ceil(aUPR * 0.12) - iUPR);
let metaCM = Math.ceil(aTZ / 3);
let metaTRAVAS = aTZ * 2;
let metaCINTAS = aTZ > 0 ? aTZ + 15 : 0;
let metaFONTES = (aTZ > 0 ? aTZ + 15 : 0) + (aUPR > 0 ? aUPR + 5 : 0);
if(invTarget['TZPR']) { invTarget['TZPR'].ideal = metaTZPR; invTarget['TZPR'].baixo = Math.ceil(metaTZPR * 0.5); }
if(invTarget['UPR']) { invTarget['UPR'].ideal = metaUPR; invTarget['UPR'].baixo = Math.ceil(metaUPR * 0.5); }
if(invTarget['CM']) { invTarget['CM'].ideal = metaCM; invTarget['CM'].baixo = Math.ceil(metaCM * 0.5); }
if(invTarget['TRAVAS']) { invTarget['TRAVAS'].ideal = metaTRAVAS; invTarget['TRAVAS'].baixo = Math.ceil(metaTRAVAS * 0.5); }
if(invTarget['CINTAS']) { invTarget['CINTAS'].ideal = metaCINTAS; invTarget['CINTAS'].baixo = Math.ceil(metaCINTAS * 0.5); }
if(invTarget['FONTE']) { invTarget['FONTE'].ideal = metaFONTES; invTarget['FONTE'].baixo = Math.ceil(metaFONTES * 0.5); }
});
if (window.dbData.inventory) {
window.dbData.inventory.metadataSac = {
data: new Date().toLocaleString('pt-BR'),
responsavel: currentUser.nome
};
}
saveToCloud(['inventory', 'nucleosInventory']).then(() => { closeModal('modal-ativos'); showToast("Base atualizada. Metas recalculadas com Inspeção!"); renderFluxoEstoque(); renderDashboard(); renderNucleoDashboard(); window.renderPainelExecutivo(); });
});
window.gerarPDFFluxo = () => {
let bD = [];
const listaCompleta = ['Central BH', ...nucleosNames];
listaCompleta.forEach(n => {
if(n === 'Extensão Gameleira' || n === 'Admissão/NSI') return;
let inv = n === 'Central BH' ? window.dbData.inventory : window.dbData.nucleosInventory[n];
if(inv && (inv.ativosTZPR > 0 || inv.ativosUPR > 0)) {
let getF = (it) => {
let atual = inv[it]?.atual || 0; let meta = inv[it]?.ideal || 0;
if(atual < meta) return `Falta ${meta - atual} (${atual}/${meta})`;
if(atual > meta) return `Sobra ${atual - meta} (${atual}/${meta})`;
return `OK (${atual}/${meta})`;
};
let eTZ = inv.estoqueSacTZPR || 0; let sigmeTZ = inv['TZPR']?.atual || 0; let iTZ = inv.inspecaoTZPR || 0;
let divMsg = (sigmeTZ !== eTZ) ? `DIVERGÊNCIA\nSAC: ${eTZ} | FIS: ${sigmeTZ}` : 'OK';
if(iTZ > 5) divMsg += `\nINSP: ${iTZ} peças`;
bD.push([
n.replace("Núcleo", "N.").replace("Subnúcleo", "S."),
getF('TZPR'), getF('UPR'), getF('CM'),
getF('TRAVAS'), getF('CINTAS'), getF('FONTE'), divMsg
]);
}
});
window.gerarPdfUniversal({ tipo: 'tabela_relatorio', orientacao: 'landscape', titulo: 'MÉTRICA PROPORCIONAL DE LIMITES GLOBAIS E DIVERGÊNCIAS', subtitulo: `Mapeamento das Necessidades baseado na taxa de Ativos e na Fórmula Integral dos Acessórios.\nData da Extração: ${new Date().toLocaleString('pt-BR')} | Gestor: ${currentUser.nome} (${currentUser.masp})`, head: ['Pólo Regional', 'Status TZPR', 'Status UPR', 'Status CM', 'Status TRAVAS', 'Status CINTAS', 'Status FONTES', 'Auditoria Estoque'], body: bD, nomeArquivo: `Analise_Limites_${new Date().getTime()}.pdf` });
};
/* --- PATCH DEFINITIVO SPACECOM --- */
setTimeout(function(){
/* PDF SPACECOM - PADRÃO CORPORATIVO */
window.gerarPdfSpacecom = async function(loteId, origem, itensPendentes, meta = {}) {
const jspdfObj = (window.jspdf && window.jspdf.jsPDF) ? window.jspdf.jsPDF : window.jsPDF;
if (!jspdfObj) throw new Error('Biblioteca jsPDF não carregada');
const doc = new jspdfObj({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const left = 14;
const center = 105;
const pageW = 210;
const pageH = 297;
let minY = 48;
let maxY = 246;
let y = minY;
const dataFormatada = new Date().toLocaleDateString('pt-BR');
const dataHoraAgora = new Date().toLocaleString('pt-BR');
const nomeColaborador = window.currentUser ? window.currentUser.nome : 'Colaborador Spacecom';
const isColaboradorSpacecom = true;
let logoSpacecomData = null;
let modeloPdfSpacecomData = null;
let headerSpacecomData = null;
let footerSpacecomData = null;
if (isColaboradorSpacecom) {
modeloPdfSpacecomData = await carregarPrimeiraImagem([
'modelopdfspacecom.png',
'modelopdfspacecom.PNG',
'modelo pdf spacecom.png',
'modelo_pdf_spacecom.png',
'modelo-pdf-spacecom.png',
'modelopdfspacecom.jpg',
'modelopdfspacecom.jpeg'
]);
if (!modeloPdfSpacecomData) {
modeloPdfSpacecomData = await carregarPrimeiraPaginaPdfComoImagem([
'modelopdfspacecom.pdf',
'modelo pdf spacecom.pdf',
'modelo_pdf_spacecom.pdf',
'modelo-pdf-spacecom.pdf',
'recibo spacecom modelo.pdf'
]);
}
}
// Se não houver template completo, busca assets de fallback.
if (!modeloPdfSpacecomData) {
try {
logoSpacecomData = await carregarImagem('logospacecom.png');
} catch (e) {
logoSpacecomData = null;
console.log('Logo da Spacecom não carregada:', e);
}
}
if (isColaboradorSpacecom && modeloPdfSpacecomData) {
minY = 60;
maxY = 240;
y = minY;
} else if (isColaboradorSpacecom) {
console.warn('[PDF Spacecom] Modelo de template não encontrado: modelopdfspacecom.(png/jpg). Usando layout alternativo.');
}
if (isColaboradorSpacecom && !modeloPdfSpacecomData) {
headerSpacecomData = await carregarPrimeiraImagem([
'spacecom-cabecalho.png',
'cabecalho-spacecom.png',
'cabecalho_spacecom.png',
'spacecom-header.png',
'header-spacecom.png',
'header_spacecom.png'
]);
footerSpacecomData = await carregarPrimeiraImagem([
'spacecom-rodape.png',
'rodape-spacecom.png',
'rodape_spacecom.png',
'spacecom-footer.png',
'footer-spacecom.png',
'footer_spacecom.png'
]);
}
const drawCorporateTemplate = function() {
if (isColaboradorSpacecom && modeloPdfSpacecomData) {
doc.addImage(modeloPdfSpacecomData, 'PNG', 0, 0, pageW, pageH);
return;
}
doc.setDrawColor(205, 205, 205);
doc.setLineWidth(0.55);
// Linhas mestre (topo e rodapé)
doc.line(14, 34, 196, 34);
doc.line(14, 268, 196, 268);
if (isColaboradorSpacecom && headerSpacecomData) {
doc.addImage(headerSpacecomData, 'PNG', 14, 8, 182, 24);
} else {
// Elementos gráficos superiores (linhas curvas estilizadas)
doc.setDrawColor(210, 210, 210);
doc.setLineWidth(0.35);
for (let i = 0; i < 7; i++) {
const off = i * 3.4;
doc.lines([[34, -8], [26, 10], [22, 18], [14, 16]], 0, 8 + off);
}
}
if (!isColaboradorSpacecom || !footerSpacecomData) {
// Elementos gráficos inferiores (linhas curvas estilizadas)
for (let i = 0; i < 7; i++) {
const off = i * 3.2;
doc.lines([[-30, 8], [-24, -2], [-18, -14], [-12, -20]], pageW, pageH - 4 - off);
}
}
// Rodapé da Spacecom (endereço)
doc.setFont('helvetica', 'normal');
doc.setFontSize(7.6);
doc.setTextColor(120, 120, 120);
if (isColaboradorSpacecom && footerSpacecomData) {
doc.addImage(footerSpacecomData, 'PNG', 14, 268.5, 182, 24);
} else {
doc.text('Spacecomm Monitoramento S/A', 14, 274.5);
doc.text('CNPJ: 09.070.101/0001-03', 14, 278.5);
doc.text('Rua Professor Pedro Viriato Parigot de Souza, 3901 - 11° Andar', 14, 282.5);
doc.text('Condomínio Reserva Ecoville - Bloco Torre Comercial', 14, 286.5);
doc.text('Curitiba - Paraná, CEP 81.280-330', 14, 290.5);
doc.text('Tel.: (41) 3270-6000', 14, 294.2);
}
// Logo da Spacecom (canto superior esquerdo)
if (logoSpacecomData) doc.addImage(logoSpacecomData, 'PNG', 16, 11, 53, 14.8);
if (isColaboradorSpacecom) {
doc.setFont('helvetica', 'italic');
doc.setFontSize(7.6);
doc.setTextColor(90, 90, 90);
doc.text('Confiabilidade e Segurança', 16, 28.5);
}
// Selo ISO (bloco superior direito)
doc.setDrawColor(95, 95, 95);
doc.setLineWidth(0.3);
doc.roundedRect(156, 11, 39, 14, 3, 3);
doc.setFont('helvetica', 'bold');
doc.setTextColor(40, 40, 40);
doc.setFontSize(3.6);
doc.text('CERTIFIED', 158.5, 15.3);
doc.setFontSize(8.8);
doc.text('ISO 9001', 158.2, 19.8);
doc.setFontSize(4.8);
doc.text('BUREAU VERITAS', 178.4, 19.8);
};
const runAutoTable = function(options) {
if (typeof doc.autoTable === 'function') {
doc.autoTable(options);
return;
}
if (window.jspdf && typeof window.jspdf.autoTable === 'function') {
window.jspdf.autoTable(doc, options);
return;
}
if (typeof window.autoTable === 'function') {
window.autoTable(doc, options);
return;
}
throw new Error('Plugin jsPDF AutoTable não carregado');
};
const grupos = {
TZPR: { qtd: 0, seriais: [] },
CM: { qtd: 0, seriais: [] },
UPRS: { qtd: 0, seriais: [] },
TRAVAS: { qtd: 0, seriais: [] }
};
const itensSemSerial = {
CINTAS: 0,
FONTE: 0,
MANUAL_MONIT: 0,
MANUAL_VITIMA: 0,
MANUAL_APP: 0
};
const normalizarTipo = function(tipo) {
const t = String(tipo || '').toUpperCase();
if (t.includes('TZPR')) return 'TZPR';
if (t.includes('UPR')) return 'UPRS';
if (t === 'CM' || t.includes(' CM') || t.includes('(CM') || t.includes('TORNOZELEIRA CM')) return 'CM';
if (t.includes('TRAVA')) return 'TRAVAS';
return null;
};
const normalizarSemSerial = function(tipo) {
const t = String(tipo || '').toUpperCase();
if (t.includes('CINTA')) return 'CINTAS';
if (t.includes('FONTE')) return 'FONTE';
if (t.includes('MANUAL_MONIT')) return 'MANUAL_MONIT';
if (t.includes('MANUAL_VITIMA')) return 'MANUAL_VITIMA';
if (t.includes('MANUAL_APP')) return 'MANUAL_APP';
return null;
};
const extrairSeriais = function(raw) {
if (!raw || typeof raw !== 'string') return [];
if (typeof window.expandirSeriais === 'function') {
return window.expandirSeriais(raw).filter(function(s) {
return s && s !== 'Nenhum serial informado / Range omitido';
});
}
return raw.split(/[\s,;\n\r]+/).map(function(s) { return s.trim(); }).filter(Boolean);
};
(itensPendentes || []).forEach(function(it) {
const key = normalizarTipo(it.item);
const qtd = parseInt(it.qtd, 10) || 0;
if (key && grupos[key]) {
grupos[key].qtd += qtd;
const seriais = extrairSeriais(it.seriais);
if (seriais.length > 0) grupos[key].seriais = grupos[key].seriais.concat(seriais);
return;
}
const semSerial = normalizarSemSerial(it.item);
if (semSerial && Object.prototype.hasOwnProperty.call(itensSemSerial, semSerial)) {
itensSemSerial[semSerial] += qtd;
}
});
Object.keys(grupos).forEach(function(k) {
const g = grupos[k];
while (g.seriais.length < g.qtd) g.seriais.push('x');
if (g.qtd > 0 && g.seriais.length > g.qtd) g.seriais = g.seriais.slice(0, g.qtd);
});
const totalEquipamentos = Object.values(grupos).reduce(function(acc, g) {
return acc + (parseInt(g.qtd, 10) || 0);
}, 0) + Object.values(itensSemSerial).reduce(function(acc, qtd) {
return acc + (parseInt(qtd, 10) || 0);
}, 0);
const addText = function(texto, opt) {
const fs = (opt && opt.fs) || 10;
const bold = !!(opt && opt.bold);
const color = (opt && opt.color) || [0, 0, 0];
const align = (opt && opt.align) || 'left';
const step = (opt && opt.step) || 5;
const x = align === 'center' ? center : left;
if (y > maxY) {
doc.addPage();
y = minY;
drawCorporateTemplate();
}
doc.setFont('helvetica', bold ? 'bold' : 'normal');
doc.setFontSize(fs);
doc.setTextColor(color[0], color[1], color[2]);
doc.text(String(texto), x, y, { align: align });
y += step;
};
const addWrap = function(texto, width, opt) {
const fs = (opt && opt.fs) || 10;
const bold = !!(opt && opt.bold);
const step = (opt && opt.step) || 5;
doc.setFont('helvetica', bold ? 'bold' : 'normal');
doc.setFontSize(fs);
doc.setTextColor(0, 0, 0);
const linhas = doc.splitTextToSize(String(texto), width || 182);
linhas.forEach(function(l) {
if (y > maxY) {
doc.addPage();
y = minY;
drawCorporateTemplate();
}
doc.text(l, left, y);
y += step;
});
};
const gerarQrAssinatura = async (payload) => gerarQrDataUrl(payload);
const montarLinhasSeriais = function(lista, colunas) {
const dados = [];
const src = (lista && lista.length > 0) ? lista.slice() : ['x'];
while (src.length % colunas !== 0) src.push('x');
for (let i = 0; i < src.length; i += colunas) {
dados.push(src.slice(i, i + colunas));
}
return dados;
};
const desenharGrupo = function(rotulo, key) {
const g = grupos[key] || { qtd: 0, seriais: [] };
if (!g.qtd) return;
addText(rotulo + ': ' + String(g.qtd).padStart(2, '0'), { fs: 10.5, bold: true, align: 'center', step: 2.5 });
const body = montarLinhasSeriais(g.seriais, 5);
runAutoTable({
startY: y,
body: body,
theme: 'grid',
margin: { left: left, right: left },
styles: {
font: 'helvetica',
fontSize: 9,
cellPadding: 1.4,
halign: 'center',
valign: 'middle',
lineColor: [0, 0, 0],
lineWidth: 0.25,
textColor: [0, 0, 0]
},
willDrawPage: function(data) {
if ((data && data.pageNumber || 1) > 1) {
drawCorporateTemplate();
}
}
});
y = (doc.lastAutoTable ? doc.lastAutoTable.finalY : y) + 5;
};
drawCorporateTemplate();
addText('Data:' + dataFormatada, { fs: 12.5, step: 8 });
addText('RECIBO DE ENTREGA DISPOSITIVOS', { fs: 14, bold: true, color: [220, 0, 0], align: 'center', step: 11 });
addText('MG - SEJUSP', { fs: 14, bold: true, step: 6.2 });
addText('Secretaria de Estado de Justiça e Segurança Pública - SEJUSP', { fs: 11.8, step: 5.6 });
addText('Diretoria de Gestão e Monitoramento Eletrônico - DME', { fs: 11.8, step: 5.6 });
addText('CNPJ: 05.487.631/0001-09', { fs: 11.8, step: 5.6 });
addWrap('Endereço: Rodovia Papa João Paulo II, 3777 - Edifício Minas - Bairro Serra Verde - Belo Horizonte. CEP 31630-903', 182, { fs: 11.2, step: 5.4 });
y += 4;
const unidadesUsuarioPdf = Array.isArray(window.currentUser?.unidades)
? window.currentUser.unidades
: (typeof window.currentUser?.unidades === 'string' ? window.currentUser.unidades.split(',').map(s => s.trim()).filter(Boolean) : []);
const unidadeLogadaPdf = unidadesUsuarioPdf.find(u => u && String(u).trim().toLowerCase() !== 'spacecom') || 'Central BH';
addWrap('Eu, ' + nomeColaborador + ', funcionário da empresa Spacecomm Monitoramento S/A, repasso os seguintes dispositivos pré-triados ao setor de estoque da ' + unidadeLogadaPdf + ':', 182, { fs: 11.6, step: 6.2 });
const resumoSemSerial = Object.keys(itensSemSerial)
.filter(function(key) { return (parseInt(itensSemSerial[key], 10) || 0) > 0; })
.map(function(key) { return String(itensSemSerial[key]).padStart(2, '0') + ' ' + window.formatItemName(key); })
.join(', ');
const textoQuantitativo = resumoSemSerial
? 'Quantitativo total de equipamentos: ' + String(totalEquipamentos).padStart(2, '0') + ' - ' + resumoSemSerial + '.'
: 'Quantitativo total de equipamentos: ' + String(totalEquipamentos).padStart(2, '0');
addWrap(textoQuantitativo, 182, { fs: 11.2, bold: true, step: 6.4 });
y += 3;
const temItensSerializados = Object.values(grupos).some(function(g) { return (parseInt(g.qtd, 10) || 0) > 0; });
if (temItensSerializados) {
desenharGrupo('TZPRs', 'TZPR');
desenharGrupo('CMs', 'CM');
desenharGrupo('UPRS', 'UPRS');
desenharGrupo('TRAVAS', 'TRAVAS');
}
const nomeSpacecomAssinatura = (() => {
const nMeta = String(meta.emissor?.nome || '').trim();
if (nMeta) return nMeta;
const nColab = String(window.currentUser?.nomeColaborador || '').trim();
if (nColab) return nColab;
const nAtual = String(window.currentUser?.nome || '').trim();
if (nAtual && !/policial/i.test(nAtual)) return nAtual;
return 'COLABORADOR SPACECOM';
})();
const deveIncluirAssinaturaEletronica = String(origem || '').toLowerCase().includes('saída para unidade') || !!meta.incluirAssinaturaEletronica;
const recebedorMeta = meta.recebedor || null;
const temQrPolicialRecebedor = !!(deveIncluirAssinaturaEletronica && recebedorMeta && recebedorMeta.nome);
// Reserva dinâmica do bloco para evitar quebra (e página extra só com QR).
const alturaBlocoAssinaturas = deveIncluirAssinaturaEletronica
? (temQrPolicialRecebedor ? 74 : 48)
: 78;
if (y + alturaBlocoAssinaturas > maxY) {
doc.addPage();
y = minY;
drawCorporateTemplate();
}
// Modo eletrônico: remove poluição visual do bloco manual e mantém resumo + QR.
if (!deveIncluirAssinaturaEletronica) {
addText('CONFERIDO EM ____/___/____.', { fs: 12, step: 14 });
addText('NOME DO SERVIDOR ESTADUAL (LEGÍVEL):', { fs: 12, step: 10 });
addText('ASSINATURA:', { fs: 12, step: 10 });
addText('RG ou MASP:', { fs: 12, step: 12 });
addText('NOME DO COLABORADOR SPACECOM (LEGÍVEL):', { fs: 12, step: 10 });
addText('ASSINATURA:', { fs: 12, step: 10 });
addText('RG ou MASP:', { fs: 12, step: 8 });
}
if (deveIncluirAssinaturaEletronica) {
const emissor = {
nome: nomeSpacecomAssinatura,
cpf: meta.emissor?.cpf || (window.currentUser?.cpf || ''),
cargo: 'Funcionário Spacecom',
dataHora: meta.emissor?.dataHora || dataHoraAgora
};
const recebedor = recebedorMeta;
const alturaBlocoQr = recebedor ? 66 : 36;
if (y + alturaBlocoQr > maxY) {
doc.addPage();
y = minY;
drawCorporateTemplate();
}
addText('Equipamentos conferidos eletronicamente conforme assinaturas.', { fs: 10.5, bold: true, step: 6 });
addText('ASSINATURAS ELETRÔNICAS (QR CODE)', { fs: 11, bold: true, step: 6 });
const emissorPayload = `Assinante ${String(emissor.nome || 'NAO INFORMADO')} Masp ${String(window.currentUser?.masp || window.currentUser?.cpf || emissor.cpf || '-')} Cargo/Função ${String(emissor.cargo || 'Funcionário Spacecom')} Data/Hora ${String(emissor.dataHora || dataHoraAgora)} Tipo Login/Senha Documento RECIBO_ENTREGA_DISPOSITIVOS Recibo ${String(loteId || '')}`;
const emissorQr = await gerarQrAssinatura(emissorPayload);
if (emissorQr) {
doc.addImage(emissorQr, 'PNG', left, y, 18, 18);
}
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.5);
doc.text(`Funcionário Spacecom: ${emissor.nome}`, left + 22, y + 5);
doc.text(`CPF: ${emissor.cpf || '-'}`, left + 22, y + 10);
doc.text(`Data/Hora: ${emissor.dataHora}`, left + 22, y + 15);
y += 24;
if (recebedor && recebedor.nome) {
const recDataHora = recebedor.dataHora || dataHoraAgora;
const recebedorPayload = `Assinante ${String(recebedor.nome || 'NAO INFORMADO')} Masp ${String(recebedor.masp || recebedor.cpf || '-')} Cargo/Função Policial Recebedor Data/Hora ${String(recDataHora || dataHoraAgora)} Tipo Login/Senha Documento RECIBO_ENTREGA_DISPOSITIVOS Recibo ${String(loteId || '')} Unidade ${String(recebedor.unidade || '-')}`;
const recebedorQr = await gerarQrAssinatura(recebedorPayload);
if (recebedorQr) {
doc.addImage(recebedorQr, 'PNG', left, y, 18, 18);
}
doc.setFont('helvetica', 'bold');
doc.setFontSize(9);
doc.text('Policial Recebedor', left + 22, y + 4.5);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.5);
doc.text(`Nome: ${recebedor.nome}`, left + 22, y + 9.5);
doc.text(`Masp: ${recebedor.masp || '-'} | CPF: ${recebedor.cpf || '-'}`, left + 22, y + 14.5);
doc.text(`Unidade: ${recebedor.unidade || '-'}`, left + 22, y + 19.5);
doc.text(`Data/Hora do recebimento: ${recDataHora}`, left + 22, y + 24.5);
y += 30;
}
}
doc.save('SPACECOM-RECIBO-' + (loteId || Date.now()) + '.pdf');
};
/* FUNÇÕES EXCLUSIVAS SPACECOM */
window.spacecomTriagemAtiva = null;
window.spacecomEquipamentosTriados = [];
window.spacecomSaidaItensPendentes = [];
window.itemSpacecomExigeSerial = function(item) {
return ['TZPR', 'UPR', 'CM', 'TRAVAS'].includes(String(item || '').toUpperCase());
};
window.renderSaidaSpacecomMalote = function() {
const listEl = document.getElementById('sc-saida-malote-list');
const countEl = document.getElementById('count-sc-saida-malote');
if (!listEl || !countEl) return;
if (!window.spacecomSaidaItensPendentes || window.spacecomSaidaItensPendentes.length === 0) {
listEl.innerHTML = '
Nenhum item no malote.
';
countEl.textContent = '0';
return;
}
let html = '';
let total = 0;
window.spacecomSaidaItensPendentes.forEach((it, idx) => {
total += parseInt(it.qtd, 10) || 0;
const sTxt = (it.seriais && it.seriais.length > 0) ? it.seriais.join(', ') : 'Sem serial obrigatório';
html += `
${window.formatItemName(it.item)} (${it.qtd}x)
🔹 ${sTxt}
`;
});
listEl.innerHTML = html;
countEl.textContent = String(total);
lucide.createIcons();
};
window.initPainelSaidaSpacecom = function() {
const destinoEl = document.getElementById('sc-saida-destino');
const itemEl = document.getElementById('sc-saida-item');
const itensSaida = ['TZPR', 'UPR', 'CM', 'TRAVAS', 'CINTAS', 'FONTE', 'MANUAL_MONIT', 'MANUAL_VITIMA', 'MANUAL_APP'];
const unidadesRaw = window.currentUser?.unidades;
let unidadesNormalizadas = [];
if (Array.isArray(unidadesRaw)) {
unidadesNormalizadas = unidadesRaw;
} else if (typeof unidadesRaw === 'string' && unidadesRaw.trim()) {
unidadesNormalizadas = unidadesRaw.split(',').map(s => s.trim()).filter(Boolean);
}
const vinculadas = unidadesNormalizadas.filter(u => u && String(u).trim().toLowerCase() !== 'spacecom');
const destinos = vinculadas.length > 0 ? [...new Set(vinculadas)] : ['Central BH'];
if (destinoEl) {
destinoEl.innerHTML = '';
destinos.forEach(d => {
const opt = document.createElement('option');
opt.value = d;
opt.textContent = d;
destinoEl.appendChild(opt);
});
if (destinoEl.options.length === 0) {
destinoEl.innerHTML = '
Central BH ';
}
}
if (itemEl) {
itemEl.innerHTML = '';
itensSaida.forEach(i => {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = window.formatItemName(i);
itemEl.appendChild(opt);
});
}
window.renderSaidaSpacecomMalote();
};
window.limparSaidaSpacecom = function() {
window.spacecomSaidaItensPendentes = [];
const sr = document.getElementById('sc-saida-seriais');
const qtd = document.getElementById('sc-saida-qtd');
if (sr) sr.value = '';
if (qtd) qtd.value = '1';
if (window.atualizarContagemSeriais) window.atualizarContagemSeriais('sc-saida-seriais');
window.renderSaidaSpacecomMalote();
};
window.removerItemSaidaSpacecom = function(idx) {
window.spacecomSaidaItensPendentes.splice(idx, 1);
window.renderSaidaSpacecomMalote();
};
window.adicionarItemSaidaSpacecom = function() {
const item = document.getElementById('sc-saida-item')?.value || '';
let qtd = parseInt(document.getElementById('sc-saida-qtd')?.value || '0', 10);
const seriaisRaw = document.getElementById('sc-saida-seriais')?.value || '';
if (!item) { window.showToast('Selecione o equipamento.', 'error'); return; }
if (!qtd || qtd <= 0) { window.showToast('Informe a quantidade.', 'error'); return; }
const exigeSerial = window.itemSpacecomExigeSerial(item);
const seriais = expandirSeriais(seriaisRaw).filter(s => s && s !== 'Nenhum serial informado / Range omitido');
if (exigeSerial && seriais.length === 0) {
window.showToast(`Para ${window.formatItemName(item)}, informe os seriais bipados.`, 'error');
return;
}
if (exigeSerial && seriais.length !== qtd) {
qtd = seriais.length;
const qtdEl = document.getElementById('sc-saida-qtd');
if (qtdEl) qtdEl.value = String(qtd);
}
const serialSet = new Set();
(window.spacecomSaidaItensPendentes || []).forEach(it => (it.seriais || []).forEach(s => serialSet.add(String(s).trim().toUpperCase())));
for (const s of seriais) {
const key = String(s).trim().toUpperCase();
if (serialSet.has(key)) {
window.showToast(`Serial duplicado no malote: ${s}`, 'warning');
return;
}
}
window.spacecomSaidaItensPendentes.push({ item, qtd, seriais });
document.getElementById('sc-saida-qtd').value = '1';
document.getElementById('sc-saida-seriais').value = '';
if (window.atualizarContagemSeriais) window.atualizarContagemSeriais('sc-saida-seriais');
window.renderSaidaSpacecomMalote();
window.showToast(`${window.formatItemName(item)} adicionado ao malote.`, 'success');
};
window.finalizarSaidaSpacecom = async function() {
const destino = document.getElementById('sc-saida-destino')?.value || 'Central BH';
const origemMaterial = document.getElementById('sc-saida-origem')?.value || 'Retorno de Manutenção (Limpos)';
const itens = window.spacecomSaidaItensPendentes || [];
if (itens.length === 0) { window.showToast('Adicione itens no malote antes de fechar.', 'error'); return; }
const existeReciboAtivoMesmoDestino = (window.dbData.movimentosManuais || []).some(m =>
m.tipo === 'Saída Spacecom para Unidade' &&
String(m.unidadeDestino || m.unidade || '').trim().toLowerCase() === String(destino || '').trim().toLowerCase() &&
(!m.status || ['Pendente', 'Recebido pela Spacecom', 'Em Inspeção Spacecom', 'Em Triagem'].includes(String(m.status || '').trim())) &&
!m.arquivado
);
const prefixo = (destino === 'Central BH') ? 'BH' : destino.replace(/Núcleo |Subnúcleo |Extensão /gi, '').replace(/\s+/g, '').toUpperCase().substring(0, 3);
let maxSeq = 0;
(window.dbData.movimentosManuais || []).forEach(m => {
if (m.tipo === 'Saída Spacecom para Unidade' && (m.recibo || '').startsWith(prefixo)) {
const n = parseInt(String(m.recibo || '').replace(prefixo, ''), 10);
if (!isNaN(n) && n > maxSeq) maxSeq = n;
}
});
const numeroRecibo = prefixo + String(maxSeq + 1).padStart(2, '0');
itens.forEach(it => {
window.dbData.movimentosManuais.push({
id: 'MOV-' + Date.now().toString().slice(-5) + Math.floor(Math.random() * 100),
data: new Date().toLocaleString('pt-BR'),
item: it.item,
tipo: 'Saída Spacecom para Unidade',
qtd: it.qtd,
obs: `Recibo Spacecom Nº ${numeroRecibo}. Origem Material: ${origemMaterial}. Destino: ${destino}.`,
responsavel: currentUser.nome,
cpf: currentUser.cpf || '',
unidade: destino,
unidadeDestino: destino,
origemConferencia: origemMaterial,
status: 'Pendente',
recibo: numeroRecibo,
seriais: (it.seriais && it.seriais.length > 0) ? it.seriais.join('\n') : '',
serialsBipados: (it.seriais && it.seriais.length > 0) ? it.seriais.join('\n') : ''
});
});
const itensPdf = itens.map(it => ({ item: it.item, qtd: parseInt(it.qtd, 10) || 0, seriais: (it.seriais || []).join('\n') }));
window.showToast('Gerando PDF do recibo, aguarde...', 'warning');
try {
if (typeof window.gerarPdfSpacecom === 'function') {
await window.gerarPdfSpacecom(numeroRecibo, 'Saída para Unidade', itensPdf);
} else {
await window.gerarPdfUniversal({
tipo: 'tabela_relatorio',
orientacao: 'portrait',
titulo: `RECIBO SPACECOM ${numeroRecibo}`,
subtitulo: `Origem: ${origemMaterial} | Destino: ${destino} | Responsável: ${currentUser.nome}`,
head: ['Item', 'Qtd', 'Seriais'],
body: itensPdf.map(it => [window.formatItemName(it.item), String(it.qtd), it.seriais || '-']),
nomeArquivo: `SPACECOM-RECIBO-${numeroRecibo}.pdf`
});
}
window.showToast('PDF gerado com sucesso.', 'success');
} catch (pdfErr) {
console.error('Erro ao gerar PDF da saída Spacecom:', pdfErr);
window.showToast('Falha ao gerar PDF. Verifique o bloqueio de download/pop-up do navegador.', 'error');
}
await saveToCloud(['movimentosManuais']);
window.limparSaidaSpacecom();
const msgExtra = existeReciboAtivoMesmoDestino ? ' Novo recibo para mesma unidade liberado.' : '';
window.showToast(`Malote ${numeroRecibo} fechado e enviado para ${destino}.${msgExtra}`, 'success');
};
window.renderSpacecomInspecaoCards = function() {
const grid = document.getElementById('spacecom-inspecao-grid');
if (!grid) return;
const mapa = {
TZPR: { qtd: 0, recibos: new Set() },
UPR: { qtd: 0, recibos: new Set() },
CM: { qtd: 0, recibos: new Set() },
TRAVAS: { qtd: 0, recibos: new Set() },
CINTAS: { qtd: 0, recibos: new Set() },
FONTE: { qtd: 0, recibos: new Set() },
MANUAL_MONIT: { qtd: 0, recibos: new Set() },
MANUAL_VITIMA: { qtd: 0, recibos: new Set() },
MANUAL_APP: { qtd: 0, recibos: new Set() }
};
const iconMap = {
TZPR: 'radio',
UPR: 'router',
CM: 'battery-medium',
TRAVAS: 'lock-keyhole',
CINTAS: 'link',
FONTE: 'plug-2',
MANUAL_MONIT: 'book-open',
MANUAL_VITIMA: 'book-open',
MANUAL_APP: 'smartphone'
};
const registros = (window.dbData?.movimentosManuais || []).filter(m => {
return m.tipo === 'Saída para Triagem/Manutenção' && ['Em Inspeção Spacecom', 'Em Triagem'].includes(m.status);
});
registros.forEach(m => {
const itemKey = String(m.item || '').trim().toUpperCase();
if (!mapa[itemKey]) return;
mapa[itemKey].qtd += parseInt(m.qtd || 0, 10) || 0;
mapa[itemKey].recibos.add(String(m.recibo || m.id || 'SEM-ID'));
});
const itensAtivos = Object.keys(mapa).filter(k => mapa[k].qtd > 0);
if (itensAtivos.length === 0) {
grid.innerHTML = '
Nenhum item em inspeção no momento.
';
return;
}
let html = '';
itensAtivos.forEach(key => {
const info = mapa[key];
const icon = iconMap[key] || 'box';
html += `
${window.formatItemName(key)}
Em Inspeção
${info.qtd.toLocaleString('pt-BR')}
Quantidade
${info.qtd.toLocaleString('pt-BR')}
Recibos
${info.recibos.size}
`;
});
grid.innerHTML = html;
lucide.createIcons();
};
window.carregarRecibosSpacecom = function() {
const container = document.getElementById('spacecom-recibos-pendentes');
if (!container) return;
const recibos = (window.dbData.movimentosManuais || []).filter(m =>
m.tipo === 'Saída para Triagem/Manutenção' && ['Recebido pela Spacecom', 'Em Inspeção Spacecom', 'Em Triagem'].includes(m.status)
);
if (recibos.length === 0) {
container.innerHTML = '
Nenhum recibo pendente
';
window.renderSpacecomInspecaoCards();
return;
}
const recibosMap = {};
recibos.forEach(m => {
const key = m.recibo || m.id;
if (!recibosMap[key]) {
recibosMap[key] = { id: key, data: m.data, responsavel: m.responsavel, count: 0, itens: [], unidade: m.unidade, status: m.status };
}
recibosMap[key].count += parseInt(m.qtd || 0, 10);
recibosMap[key].itens.push(`${m.qtd}x ${m.item}`);
});
const list = Object.values(recibosMap).reverse();
let html = '';
list.forEach(r => {
const dataFormatada = r.data ? r.data.split(' ')[0] : 'N/A';
html += `
${r.count} itens
${r.itens.join(', ')}
Status: ${r.status || 'N/A'}
👤 ${r.responsavel || 'N/A'}
`;
});
container.innerHTML = html;
window.renderSpacecomInspecaoCards();
lucide.createIcons();
};
window.selecionarReciboParaTriagem = function(reciboId) {
const recibo = (window.dbData.movimentosManuais || []).find(m => m.id === reciboId || m.recibo === reciboId);
if (!recibo) {
window.showToast('Recibo não encontrado', 'error');
return;
}
window.spacecomTriagemAtiva = recibo;
window.spacecomEquipamentosTriados = [];
// Mostrar painel de triagem
document.getElementById('spacecom-triagem-ativa').classList.remove('hidden');
document.getElementById('spacecom-recibo-numero').textContent = recibo.recibo || recibo.id;
document.getElementById('spacecom-triagem-tipo').textContent = recibo.tipo || 'N/A';
document.getElementById('spacecom-triagem-responsavel').textContent = recibo.responsavel || 'N/A';
// Dados esperados
document.getElementById('spacecom-triagem-item').textContent = recibo.item || '-';
document.getElementById('spacecom-triagem-qtd').textContent = recibo.qtd || '-';
document.getElementById('spacecom-triagem-motivo').textContent = recibo.motivo || '-';
document.getElementById('spacecom-triagem-obs').textContent = recibo.obs || '-';
// Inicializar dropdown com equipamentos do sistema
const selectItem = document.getElementById('spacecom-triagem-item-select');
if (selectItem && window.dbData && window.dbData.inventory) {
selectItem.innerHTML = '
Selecione um equipamento... ';
Object.keys(window.dbData.inventory).forEach(key => {
const item = window.dbData.inventory[key];
if (item && item.nome) {
const option = document.createElement('option');
option.value = key;
option.textContent = window.formatItemName(key);
selectItem.appendChild(option);
}
});
}
// Limpar lista de triados
window.renderEquipamentosTriados();
document.getElementById('spacecom-triagem-serial').value = '';
const qtdEl = document.getElementById('spacecom-triagem-qtd-input');
if (qtdEl) qtdEl.value = '1';
document.getElementById('spacecom-triagem-serial').focus();
// Scroll
setTimeout(() => {
document.querySelector('[id="spacecom-triagem-ativa"]').scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 300);
window.showToast(`Recibo ${recibo.recibo || recibo.id} carregado para triagem`, 'success');
// Marcar como em triagem para desacoplar da etapa de recebimento
(window.dbData.movimentosManuais || []).forEach(m => {
if ((m.recibo || m.id) === (recibo.recibo || recibo.id) && m.tipo === 'Saída para Triagem/Manutenção' && m.status === 'Recebido pela Spacecom') {
m.status = 'Em Triagem';
}
});
window.renderSpacecomInspecaoCards();
};
window.renderEquipamentosTriados = function() {
const container = document.getElementById('spacecom-equipamentos-triados');
const counter = document.getElementById('count-equipamentos-triados');
if (!container) return;
if (window.spacecomEquipamentosTriados.length === 0) {
container.innerHTML = '
Nenhum equipamento triado ainda
';
counter.textContent = '0';
return;
}
let html = '';
window.spacecomEquipamentosTriados.forEach((item, idx) => {
const serialInfo = item.seriais && item.seriais.length > 0
? item.seriais.join(', ')
: 'Sem serial obrigatório';
html += `
${window.formatItemName(item.item)} (${item.qtd}x)
🔹 ${serialInfo}
`;
});
container.innerHTML = html;
const total = window.spacecomEquipamentosTriados.reduce((sum, it) => sum + (parseInt(it.qtd, 10) || 0), 0);
counter.textContent = total;
lucide.createIcons();
};
window.adicionarEquipamentoTriagem = function() {
const item = document.getElementById('spacecom-triagem-item-select').value;
const qtd = parseInt(document.getElementById('spacecom-triagem-qtd-input')?.value || '0', 10);
const serialRaw = document.getElementById('spacecom-triagem-serial').value;
if (!item || !item.trim()) {
window.showToast('Selecione um equipamento', 'error');
return;
}
if (!qtd || qtd <= 0) {
window.showToast('Informe a quantidade', 'error');
return;
}
const exigeSerial = window.itemSpacecomExigeSerial(item);
const listaSeriais = expandirSeriais(serialRaw || '').filter(s => s && s !== 'Nenhum serial informado / Range omitido');
if (exigeSerial && listaSeriais.length !== qtd) {
window.showToast(`Para ${window.formatItemName(item)}, informe ${qtd} serial(ais).`, 'error');
return;
}
// Verificar serial duplicado em toda a caixa
const serialSetAtual = new Set();
(window.spacecomEquipamentosTriados || []).forEach(eq => {
(eq.seriais || []).forEach(s => serialSetAtual.add(String(s).trim().toUpperCase()));
});
for (const s of listaSeriais) {
const k = String(s).trim().toUpperCase();
if (serialSetAtual.has(k)) {
window.showToast(`Serial duplicado na caixa: ${s}`, 'warning');
return;
}
}
window.spacecomEquipamentosTriados.push({
item: item,
qtd: qtd,
seriais: listaSeriais
});
// Limpar campos
document.getElementById('spacecom-triagem-item-select').value = '';
document.getElementById('spacecom-triagem-qtd-input').value = '1';
document.getElementById('spacecom-triagem-serial').value = '';
window.atualizarContagemSeriais('spacecom-triagem-serial');
document.getElementById('spacecom-triagem-serial').focus();
window.renderEquipamentosTriados();
window.showToast(`✅ ${window.formatItemName(item)} adicionado`, 'success');
};
window.removerEquipamentoTriagem = function(idx) {
window.spacecomEquipamentosTriados.splice(idx, 1);
window.renderEquipamentosTriados();
};
window.limparSerialTriagem = function() {
document.getElementById('spacecom-triagem-serial').value = '';
window.atualizarContagemSeriais('spacecom-triagem-serial');
document.getElementById('spacecom-triagem-serial').focus();
};
window.cancelarTriagem = function() {
if (confirm('Deseja cancelar a triagem?')) {
window.spacecomTriagemAtiva = null;
window.spacecomEquipamentosTriados = [];
document.getElementById('spacecom-triagem-ativa').classList.add('hidden');
document.getElementById('spacecom-recibos-pendentes').scrollIntoView({ behavior: 'smooth' });
window.carregarRecibosSpacecom();
window.showToast('Triagem cancelada', 'success');
}
};
window.gerarReciboDevoluicao = function() {
if (!window.spacecomTriagemAtiva) {
window.showToast('Nenhuma triagem ativa', 'error');
return;
}
if (!window.spacecomEquipamentosTriados || window.spacecomEquipamentosTriados.length === 0) {
window.showToast('Adicione pelo menos um equipamento triado', 'error');
return;
}
try {
const recibo = window.spacecomTriagemAtiva;
const numeroRecibo = "DEV-" + Date.now().toString().slice(-6) + Math.floor(Math.random() * 100);
const itensParaPdf = (window.spacecomEquipamentosTriados || []).map(function(eq) {
return {
item: eq.item,
qtd: parseInt(eq.qtd, 10) || 0,
seriais: (eq.seriais && eq.seriais.length > 0) ? eq.seriais.join('\n') : 'x'
};
});
const totalQtd = itensParaPdf.reduce((sum, it) => sum + (parseInt(it.qtd, 10) || 0), 0);
if (typeof window.gerarPdfSpacecom !== 'function') {
throw new Error('Função padrão de PDF da Spacecom não encontrada');
}
// Reutiliza o mesmo layout oficial da Spacecom (RECIBO DE ENTREGA DISPOSITIVOS)
window.gerarPdfSpacecom(numeroRecibo, 'Devolução', itensParaPdf);
window.showToast(`PDF gerado: DEV-${numeroRecibo}`, 'success');
// Atualizar status
recibo.status = 'Devolvido ao Policial';
recibo.triagemdoConcluida = new Date().toLocaleString('pt-BR');
recibo.triademPor = window.currentUser.nome;
recibo.equipamentoTriados = totalQtd;
// Atualizar todos os movimentos do recibo original
(window.dbData.movimentosManuais || []).forEach(m => {
if ((m.recibo || m.id) === (recibo.recibo || recibo.id) && m.tipo === 'Saída para Triagem/Manutenção') {
m.status = 'Devolvido ao Policial';
m.devolucaoData = new Date().toLocaleString('pt-BR');
m.devolucaoPor = window.currentUser.nome;
}
});
// Adicionar movimento
window.dbData.movimentosManuais.push({
id: 'TRI-' + Date.now().toString().slice(-6) + Math.floor(Math.random() * 100),
data: new Date().toLocaleString('pt-BR'),
item: 'MALOTE DEVOLUÇÃO',
tipo: 'Saída Spacecom para Policial',
qtd: totalQtd,
itens: itensParaPdf,
seriais: itensParaPdf.map(e => `${window.formatItemName(e.item)}: ${e.seriais || 'x'}`).join('\n'),
serialsBipados: itensParaPdf.map(e => `${window.formatItemName(e.item)}: ${e.seriais || 'x'}`).join('\n'),
obs: `Saída para policial. Recibo Devolução: ${numeroRecibo}. Itens: ${itensParaPdf.map(e => `${e.qtd}x ${window.formatItemName(e.item)}`).join(', ')}`,
responsavel: window.currentUser.nome,
recibo: numeroRecibo,
reciboOriginal: recibo.recibo || recibo.id,
status: 'Concluído',
unidade: 'Spacecom'
});
saveToCloud(['movimentosManuais']).then(() => {
window.spacecomTriagemAtiva = null;
window.spacecomEquipamentosTriados = [];
document.getElementById('spacecom-triagem-ativa').classList.add('hidden');
window.carregarRecibosSpacecom();
window.showToast(`Triagem finalizada e PDF salvo!`, 'success');
}).catch(err => {
window.showToast(`PDF gerado, mas erro ao salvar: ${err}`, 'warning');
});
} catch(err) {
window.showToast(`Erro ao gerar PDF: ${err.message}`, 'error');
console.error('Erro PDF:', err);
}
};
/* INICIALIZAR ITEMS SPACECOM */
window.initSpacecomItems = function() {
if (window.currentUser) {
if (typeof window.ensureSpacecomSaidaSelects === 'function') window.ensureSpacecomSaidaSelects();
const selectItem = document.getElementById('spacecom-triagem-item-select');
if (selectItem) {
selectItem.innerHTML = '
Selecione um equipamento... ';
const itensSaida = ['TZPR', 'UPR', 'CM', 'TRAVAS', 'CINTAS', 'FONTE', 'MANUAL_MONIT', 'MANUAL_VITIMA', 'MANUAL_APP'];
itensSaida.forEach(key => {
const option = document.createElement('option');
option.value = key;
option.textContent = window.formatItemName(key);
selectItem.appendChild(option);
});
}
window.initPainelSaidaSpacecom();
if (typeof window.ensureSpacecomSaidaSelects === 'function') window.ensureSpacecomSaidaSelects();
window.renderSpacecomInspecaoCards();
// Carregar recibos pendentes
if (typeof window.carregarRecibosSpacecom === 'function') {
setTimeout(() => window.carregarRecibosSpacecom(), 500);
}
// Carregar recebimentos pendentes
if (typeof window.carregarRecebimentosSpacecom === 'function') {
setTimeout(() => window.carregarRecebimentosSpacecom(), 500);
}
}
};
/* ===== RECEBIMENTO SPACECOM ===== */
window.spacecomRecebimentoAtivo = null;
window.spacecomRecebimentoOriginais = [];
window.spacecomRecebimentoValidados = [];
window.spacecomRecebimentoFalhas = [];
window.spacecomRecebimentoSobras = [];
window.spacecomRecebimentoPares = [];
window.spacecomRecebimentosHistoricoAtivo = false;
window.spacecomRecebimentosPagina = 1;
window.spacecomRecebimentosPorPagina = 5;
window.criarReciboManualSpacecom = function() {
const now = new Date();
const token = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}-${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
return `FIS-${token}`;
};
window.montarRecebimentoManualSpacecom = function() {
const reciboInput = document.getElementById('spacecom-receb-manual-recibo');
const responsavelInput = document.getElementById('spacecom-receb-manual-responsavel');
const itemSelect = document.getElementById('spacecom-receb-manual-item');
const reciboId = (reciboInput?.value || '').trim() || window.criarReciboManualSpacecom();
const responsavel = (responsavelInput?.value || '').trim() || 'RECIBO FÍSICO';
const item = (itemSelect?.value || '').trim() || 'TZPR';
return {
id: reciboId,
recibo: reciboId,
responsavel,
qtd: 0,
totalQty: 0,
item,
obs: 'Recibo físico importado manualmente',
expectedSeriais: '',
manual: true
};
};
window.iniciarConferenciaManualSpacecom = function(showToast = true) {
const manual = window.montarRecebimentoManualSpacecom();
const reciboInput = document.getElementById('spacecom-receb-manual-recibo');
const responsavelInput = document.getElementById('spacecom-receb-manual-responsavel');
const itemSelect = document.getElementById('spacecom-receb-manual-item');
if (reciboInput && !reciboInput.value.trim()) reciboInput.value = manual.id;
if (responsavelInput && !responsavelInput.value.trim()) {
responsavelInput.value = String(window.currentUser?.nome || '').toUpperCase();
manual.responsavel = responsavelInput.value;
}
if (itemSelect && !itemSelect.value && itemSelect.options.length > 0) {
itemSelect.value = 'TZPR';
manual.item = itemSelect.value;
}
window.spacecomRecebimentoAtivo = manual;
window.spacecomRecebimentoOriginais = [];
window.spacecomRecebimentoValidados = [];
window.spacecomRecebimentoFalhas = [];
window.spacecomRecebimentoSobras = [];
window.spacecomRecebimentoPares = [];
const reciboEl = document.getElementById('spacecom-receb-numero');
const respEl = document.getElementById('spacecom-receb-responsavel');
const colabNomeEl = document.getElementById('spacecom-receb-colab-nome');
const colabCpfEl = document.getElementById('spacecom-receb-colab-cpf');
if (reciboEl) reciboEl.textContent = manual.id;
if (respEl) respEl.textContent = manual.responsavel;
if (colabNomeEl) colabNomeEl.textContent = window.currentUser?.nome || 'NÃO INFORMADO';
if (colabCpfEl) colabCpfEl.textContent = window.currentUser?.cpf ? window.formatarCpf(window.currentUser.cpf) : 'CPF NÃO CADASTRADO';
const resDiv = document.getElementById('spacecom-receb-resultado');
if (resDiv) resDiv.classList.add('hidden');
document.getElementById('btn-pdf-receb-spacecom')?.classList.add('hidden');
document.getElementById('btn-confirmar-receb-spacecom')?.classList.add('hidden');
const expectedEl = document.getElementById('spacecom-receb-esperados');
if (expectedEl) {
expectedEl.value = '';
expectedEl.placeholder = 'Cole ou importe os seriais esperados da nota...';
}
document.getElementById('spacecom-receb-bipados').value = '';
window.atualizarContagemSeriais('spacecom-receb-bipados');
if (window.atualizarContagemSeriais) window.atualizarContagemSeriais('spacecom-receb-esperados');
setTimeout(() => document.getElementById('spacecom-receb-bipados')?.focus(), 150);
if (showToast) window.showToast('Modo Recibo Físico ativo para conferência.', 'success');
};
window.mostrarHistoricoRecebimentosSpacecom = function() {
window.spacecomRecebimentosHistoricoAtivo = true;
window.spacecomRecebimentosPagina = 1;
window.carregarRecebimentosSpacecom(true);
window.showToast('Histórico de recibos carregado.', 'success');
};
window.voltarPendenciasRecebimentosSpacecom = function() {
window.spacecomRecebimentosHistoricoAtivo = false;
window.spacecomRecebimentosPagina = 1;
window.carregarRecebimentosSpacecom(false);
window.showToast('Fila atual de recebimento carregada.', 'success');
};
window.mudarPaginaRecebimentosSpacecom = function(delta) {
const next = (window.spacecomRecebimentosPagina || 1) + delta;
if (next < 1) return;
window.spacecomRecebimentosPagina = next;
window.carregarRecebimentosSpacecom(window.spacecomRecebimentosHistoricoAtivo);
};
window.limparRecibosConferidosSpacecom = function() {
if (!confirm('Deseja limpar (arquivar) os recibos já conferidos da Spacecom?')) return;
let alterados = 0;
(window.dbData.movimentosManuais || []).forEach(m => {
const isRecebimento = m.tipo === 'Recebimento Spacecom';
const isSaidaTriagem = m.tipo === 'Saída para Triagem/Manutenção';
const isConferido = ['Em Inspeção Spacecom', 'Recebido pela Spacecom', 'Devolvido ao Policial', 'Recebido pela Unidade', 'Em Triagem', 'Concluído'].includes(m.status);
if ((isRecebimento || isSaidaTriagem) && isConferido && !m.arquivado) {
m.arquivado = true;
alterados++;
}
});
if (alterados === 0) {
window.showToast('Nenhum recibo conferido para limpar.', 'warning');
return;
}
saveToCloud(['movimentosManuais']).then(() => {
window.spacecomRecebimentosHistoricoAtivo = false;
window.carregarRecebimentosSpacecom(false);
window.showToast(`${alterados} registro(s) conferido(s) arquivado(s).`, 'success');
});
};
window.formatarCpf = function(cpfRaw) {
const s = String(cpfRaw || '').replace(/\D/g, '');
if (s.length !== 11) return '';
return s.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
};
window.getOrigemSetorSpacecom = function(recibos) {
const lista = recibos || window.spacecomRecebimentoOriginais || [];
if (!lista.length) return 'NÃO INFORMADO';
const primeiro = lista[0] || {};
if (primeiro.origem) return primeiro.origem;
if (primeiro.unidade) return primeiro.unidade;
const obs = String(primeiro.obs || '');
const match = obs.match(/Origem:\s*([^\.\n]+)/i);
return match ? match[1].trim() : 'NÃO INFORMADO';
};
window.getPolicialMaspSpacecom = function(recibos) {
const lista = recibos || window.spacecomRecebimentoOriginais || [];
if (!lista.length) return 'NÃO INFORMADO';
const p = lista.find(x => x.masp || x.registradoMasp || x.maspRepassador) || lista[0];
return p.masp || p.registradoMasp || p.maspRepassador || 'NÃO INFORMADO';
};
window.gerarPdfRecebimentoInspecaoSpacecom = function() {
if (!window.spacecomRecebimentoAtivo) {
window.showToast('Nenhum recibo carregado para recebimento.', 'error');
return;
}
if (!window.spacecomRecebimentoValidados || window.spacecomRecebimentoValidados.length === 0) {
window.showToast('Verifique os seriais antes de gerar o PDF de recebimento.', 'error');
return;
}
const recibo = window.spacecomRecebimentoAtivo;
const listaOriginais = window.spacecomRecebimentoOriginais || [];
const policialNome = recibo.responsavel || 'NÃO INFORMADO';
const policialMasp = window.getPolicialMaspSpacecom(listaOriginais);
const origemSetor = window.getOrigemSetorSpacecom(listaOriginais);
const colaboradorNome = window.currentUser?.nome || 'NÃO INFORMADO';
const colaboradorCpf = window.currentUser?.cpf ? window.formatarCpf(window.currentUser.cpf) : 'NÃO INFORMADO';
const itensParaPdf = listaOriginais.length > 0
? listaOriginais.map(m => ({
item: m.item,
qtd: parseInt(m.qtd || 0, 10) || 0,
seriais: m.seriais || m.serialsBipados || ''
}))
: [{
item: recibo.item || (document.getElementById('spacecom-receb-manual-item')?.value || 'TZPR'),
qtd: (window.spacecomRecebimentoValidados || []).length,
seriais: (window.spacecomRecebimentoValidados || []).join('\n')
}];
// Usa um molde corporativo curto, sem cabeçalho estatal e sem paginação duplicada.
window.gerarPdfRecebimentoSpacecom({
reciboId: recibo.recibo || recibo.id,
origemSetor,
policialNome,
policialMasp,
colaboradorNome,
colaboradorCpf,
itens: itensParaPdf
});
};
window.gerarPdfRecebimentoSpacecom = async function(dados) {
const jspdfObj = (window.jspdf && window.jspdf.jsPDF) ? window.jspdf.jsPDF : window.jsPDF;
if (!jspdfObj) throw new Error('Biblioteca jsPDF não carregada');
const doc = new jspdfObj({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const left = 14;
const right = 196;
const center = 105;
let contentTop = 38;
let contentBottom = 262;
const contentWidth = right - left;
const dataHoje = new Date().toLocaleDateString('pt-BR');
const dataHoraAgora = new Date().toLocaleString('pt-BR');
const isColaboradorSpacecom = true;
let y = contentTop;
let logoSpacecomData = null;
let modeloPdfSpacecomData = null;
let headerSpacecomData = null;
let footerSpacecomData = null;
try {
logoSpacecomData = await carregarImagem('logospacecom.png');
} catch (e) {
logoSpacecomData = null;
}
if (isColaboradorSpacecom) {
modeloPdfSpacecomData = await carregarPrimeiraImagem([
'modelopdfspacecom.png',
'modelopdfspacecom.PNG',
'modelo pdf spacecom.png',
'modelo_pdf_spacecom.png',
'modelo-pdf-spacecom.png',
'modelopdfspacecom.jpg',
'modelopdfspacecom.jpeg'
]);
if (!modeloPdfSpacecomData) {
modeloPdfSpacecomData = await carregarPrimeiraPaginaPdfComoImagem([
'modelopdfspacecom.pdf',
'modelo pdf spacecom.pdf',
'modelo_pdf_spacecom.pdf',
'modelo-pdf-spacecom.pdf',
'recibo spacecom modelo.pdf'
]);
}
headerSpacecomData = await carregarPrimeiraImagem([
'spacecom-cabecalho.png',
'cabecalho-spacecom.png',
'cabecalho_spacecom.png',
'spacecom-header.png',
'header-spacecom.png',
'header_spacecom.png'
]);
footerSpacecomData = await carregarPrimeiraImagem([
'spacecom-rodape.png',
'rodape-spacecom.png',
'rodape_spacecom.png',
'spacecom-footer.png',
'footer-spacecom.png',
'footer_spacecom.png'
]);
}
if (isColaboradorSpacecom && modeloPdfSpacecomData) {
contentTop = 60;
contentBottom = 240;
y = contentTop;
} else if (isColaboradorSpacecom) {
console.warn('[PDF Spacecom] Modelo de template não encontrado: modelopdfspacecom.(png/jpg). Usando layout alternativo.');
}
const drawTemplate = function() {
if (isColaboradorSpacecom && modeloPdfSpacecomData) {
doc.addImage(modeloPdfSpacecomData, 'PNG', 0, 0, 210, 297);
return;
}
doc.setDrawColor(205, 205, 205);
doc.setLineWidth(0.55);
doc.line(14, 34, 196, 34);
doc.line(14, 268, 196, 268);
if (isColaboradorSpacecom && headerSpacecomData) {
doc.addImage(headerSpacecomData, 'PNG', 14, 8, 182, 24);
}
if (logoSpacecomData) doc.addImage(logoSpacecomData, 'PNG', 16, 11, 53, 14.8);
if (isColaboradorSpacecom) {
doc.setFont('helvetica', 'italic');
doc.setFontSize(7.6);
doc.setTextColor(90, 90, 90);
doc.text('Confiabilidade e Segurança', 16, 28.5);
}
doc.setDrawColor(95, 95, 95);
doc.setLineWidth(0.3);
doc.roundedRect(156, 11, 39, 14, 3, 3);
doc.setFont('helvetica', 'bold');
doc.setTextColor(40, 40, 40);
doc.setFontSize(3.6);
doc.text('CERTIFIED', 158.5, 15.3);
doc.setFontSize(8.8);
doc.text('ISO 9001', 158.2, 19.8);
doc.setFontSize(4.8);
doc.text('BUREAU VERITAS', 178.4, 19.8);
doc.setFont('helvetica', 'normal');
doc.setFontSize(7.2);
doc.setTextColor(120, 120, 120);
if (isColaboradorSpacecom && footerSpacecomData) {
doc.addImage(footerSpacecomData, 'PNG', 14, 268.5, 182, 24);
} else {
doc.text('Spacecomm Monitoramento S/A', 14, 274.5);
doc.text('CNPJ: 09.070.101/0001-03', 14, 278.5);
doc.text('Rua Professor Pedro Viriato Parigot de Souza, 3901 - 11 Andar', 14, 282.5);
doc.text('Condominio Reserva Ecoville - Bloco Torre Comercial', 14, 286.5);
doc.text('Curitiba - Parana, CEP 81.280-330', 14, 290.5);
doc.text('Tel.: (41) 3270-6000', 14, 294.2);
}
};
const addNewPage = function() {
doc.addPage();
drawTemplate();
y = contentTop;
};
const ensureSpace = function(heightNeeded) {
if (y + heightNeeded > contentBottom) addNewPage();
};
const writeWrapped = function(texto, opts = {}) {
const fontSize = opts.size || 10;
const lineHeight = opts.lineHeight || 4.6;
const spacingAfter = opts.after || 2;
const maxWidth = opts.maxWidth || contentWidth;
const x = opts.x || left;
const lines = doc.splitTextToSize(String(texto || ''), maxWidth);
const needed = (lines.length * lineHeight) + spacingAfter;
ensureSpace(needed);
doc.setFont('helvetica', opts.bold ? 'bold' : 'normal');
doc.setFontSize(fontSize);
doc.setTextColor(0, 0, 0);
if (opts.center) {
doc.text(lines, center, y, { align: 'center' });
} else {
doc.text(lines, x, y);
}
y += (lines.length * lineHeight) + spacingAfter;
};
const itens = Array.isArray(dados.itens) ? dados.itens : [];
const resumoItens = itens.length > 0
? itens.map(it => `${String(it.qtd || 0).padStart(2, '0')} ${window.formatItemName(it.item)}`).join(', ')
: 'NAO INFORMADO';
const seriaisConferidos = itens
.map(it => String(it.seriais || '').trim())
.filter(Boolean)
.join(', ')
.trim() || 'NAO INFORMADO';
const numeroTermo = dados.reciboId || '0001/2026';
drawTemplate();
y = contentTop;
doc.setFont('helvetica', 'bold');
doc.setFontSize(12);
doc.setTextColor(0, 0, 0);
doc.text('TERMO DE RECEBIMENTO E INSPECAO DE EQUIPAMENTOS', center, y, { align: 'center' });
y += 7;
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.4);
doc.text(`Empresa Recebedora: Spacecom | Termo: ${numeroTermo} | Data: ${dataHoraAgora}`, left, y);
y += 6;
const tabelaDados = [
['Policial Remetente', `${dados.policialNome || 'NAO INFORMADO'} | MASP: ${dados.policialMasp || 'NAO INFORMADO'}`],
['Setor de Origem', dados.origemSetor || 'NAO INFORMADO'],
['Itens Recebidos', resumoItens],
['Seriais Conferidos', seriaisConferidos],
['Colaborador Spacecom', `${dados.colaboradorNome || 'NAO INFORMADO'} | CPF: ${dados.colaboradorCpf || 'NAO INFORMADO'}`],
['Recibo/Referencia', dados.reciboId || 'NAO INFORMADO']
];
if (typeof doc.autoTable === 'function') {
doc.autoTable({
startY: y,
head: [['Campo', 'Informacoes']],
body: tabelaDados,
theme: 'grid',
margin: { left, right: 14, top: contentTop, bottom: 36 },
styles: {
font: 'helvetica',
fontSize: 8.4,
cellPadding: 1.8,
lineColor: [30, 30, 30],
lineWidth: 0.2,
textColor: [0, 0, 0],
overflow: 'linebreak',
valign: 'top'
},
headStyles: {
fillColor: [0, 0, 0],
textColor: [255, 255, 255],
fontStyle: 'bold'
},
columnStyles: {
0: { cellWidth: 52, fontStyle: 'bold' },
1: { cellWidth: 130 }
},
willDrawPage: function(data) {
if ((data && data.pageNumber || 1) > 1) {
drawTemplate();
}
}
});
y = (doc.lastAutoTable && doc.lastAutoTable.finalY) ? doc.lastAutoTable.finalY + 5 : y + 6;
} else {
writeWrapped('Campo: Policial Remetente', { bold: true, size: 9, after: 1 });
writeWrapped(`${dados.policialNome || 'NAO INFORMADO'} | MASP: ${dados.policialMasp || 'NAO INFORMADO'}`, { size: 9, after: 2 });
writeWrapped('Campo: Setor de Origem', { bold: true, size: 9, after: 1 });
writeWrapped(dados.origemSetor || 'NAO INFORMADO', { size: 9, after: 2 });
writeWrapped('Campo: Itens Recebidos', { bold: true, size: 9, after: 1 });
writeWrapped(resumoItens, { size: 9, after: 2 });
writeWrapped('Campo: Seriais Conferidos', { bold: true, size: 9, after: 1 });
writeWrapped(seriaisConferidos, { size: 9, after: 2 });
writeWrapped('Campo: Colaborador Spacecom', { bold: true, size: 9, after: 1 });
writeWrapped(`${dados.colaboradorNome || 'NAO INFORMADO'} | CPF: ${dados.colaboradorCpf || 'NAO INFORMADO'}`, { size: 9, after: 3 });
}
writeWrapped('OBSERVACOES:', { bold: true, size: 10, after: 2 });
const observacoesParagrafos = [
'TERMO DE RECEBIMENTO E INSPECAO DE EQUIPAMENTOS',
'Empresa Recebedora: Spacecom',
'Orgao Remetente: MG - SEJUSP',
'Secretaria de Estado de Justica e Seguranca Publica - SEJUSP',
'Diretoria de Gestao e Monitoramento Eletronico - DME',
'CNPJ: 05.487.631/0001-09',
'Endereco: Rodovia Papa Joao Paulo II, 3777 - Edificio Minas - Bairro Serra Verde - Belo Horizonte. CEP 31630-903',
`Numero do Termo: ${numeroTermo}`,
'Declaracao de Recebimento:',
`A empresa Spacecom, por meio de seu representante tecnico abaixo assinado, atesta para os devidos fins que recebeu do(a) Policial Penal ${dados.policialNome || 'NAO INFORMADO'}, MASP: ${dados.policialMasp || 'NAO INFORMADO'}, os materiais e equipamentos de monitoracao eletronica descritos neste termo.`,
'Declaro que a conferencia quantitativa dos itens foi realizada na presente data em conjunto com o agente publico responsavel. Informo, ainda, que os referidos equipamentos foram imediatamente acautelados e encaminhados ao banco de inspecao da Spacecom, onde passarao por procedimentos padronizados de limpeza, triagem e analise tecnica pormenorizada para posterior emissao de laudo de funcionamento.',
`Setor/Origem de envio: ${dados.origemSetor || 'NAO INFORMADO'}`,
`Itens recebidos: ${resumoItens}`,
`Seriais conferidos: ${seriaisConferidos}`,
`Colaborador Spacecom: ${dados.colaboradorNome || 'NAO INFORMADO'}`,
`CPF do Colaborador: ${dados.colaboradorCpf || 'NAO INFORMADO'}`,
`Data/Hora do Recebimento: ${dataHoraAgora}`
];
let obsLines = [];
observacoesParagrafos.forEach((paragrafo, idx) => {
const linhas = doc.splitTextToSize(String(paragrafo || ''), contentWidth - 6);
obsLines = obsLines.concat(linhas);
if (idx !== observacoesParagrafos.length - 1) obsLines.push('');
});
while (obsLines.length > 0) {
ensureSpace(20);
const boxTop = y;
const boxPadding = 3;
const lineHeight = 4.2;
const usableHeight = contentBottom - boxTop - (boxPadding * 2);
const maxLines = Math.max(1, Math.floor(usableHeight / lineHeight));
const chunk = obsLines.splice(0, maxLines);
const boxHeight = (boxPadding * 2) + (chunk.length * lineHeight);
doc.setDrawColor(45, 45, 45);
doc.setLineWidth(0.28);
doc.rect(left, boxTop, contentWidth, boxHeight);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.6);
doc.setTextColor(0, 0, 0);
let textY = boxTop + boxPadding + 3;
chunk.forEach(linha => {
doc.text(String(linha || ''), left + boxPadding, textY);
textY += lineHeight;
});
y = boxTop + boxHeight + 4;
if (obsLines.length > 0) addNewPage();
}
ensureSpace(28);
writeWrapped(`Colaborador Spacecom: ${dados.colaboradorNome || 'NAO INFORMADO'}`, { bold: true, size: 9.6, after: 2 });
writeWrapped(`CPF: ${dados.colaboradorCpf || 'NAO INFORMADO'}`, { size: 9.6, after: 2 });
writeWrapped(`Data/Hora do Aceite: ${dataHoraAgora}`, { size: 9.6, after: 8 });
// Função interna para gerar QR code de assinatura
const gerarQrAssinatura = async (payload) => gerarQrDataUrl(payload);
ensureSpace(34);
// Gerar QR code eletrônico do colaborador
const colaboradorPayload = `Assinante ${String(dados.colaboradorNome || window.currentUser?.nome || 'NAO INFORMADO')} Masp ${String(window.currentUser?.masp || dados.colaboradorCpf || window.currentUser?.cpf || '-')} Cargo/Função Funcionário Spacecom Data/Hora ${String(dataHoraAgora)} Tipo Login/Senha Documento TERMO_RECEBIMENTO_INSPECAO Termo ${String(dados.reciboId || '')}`;
const colaboradorQr = await gerarQrAssinatura(colaboradorPayload);
if (colaboradorQr) {
// Renderizar QR code eletrônico
doc.setFont('helvetica', 'bold');
doc.setFontSize(9.8);
doc.text('ASSINATURA ELETRÔNICA (QR CODE):', left, y);
y += 4;
doc.addImage(colaboradorQr, 'PNG', left, y, 20, 20);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8.5);
doc.text(`Colaborador: ${dados.colaboradorNome || 'NAO INFORMADO'}`, left + 24, y + 2);
doc.text(`CPF: ${dados.colaboradorCpf || 'NAO INFORMADO'}`, left + 24, y + 7);
doc.text(`Data/Hora: ${dataHoraAgora}`, left + 24, y + 12);
y += 26;
} else {
// Fallback para assinatura manual se QR code falhar
ensureSpace(18);
doc.setFont('helvetica', 'bold');
doc.setFontSize(9.8);
doc.text('ASSINATURA DO COLABORADOR SPACECOM:', left, y);
y += 10;
doc.setFont('helvetica', 'normal');
doc.line(left, y, left + 90, y);
}
doc.save(`Spacecom_Aceite_${String(dados.reciboId || Date.now()).replace(/\s+/g, '_')}.pdf`);
};
window.carregarRecebimentosSpacecom = function(showHistory = false) {
const container = document.getElementById('spacecom-receb-list');
const statusEl = document.getElementById('spacecom-receb-status');
const legacyAlertEl = document.getElementById('spacecom-receb-legacy-alert');
const paginacaoEl = document.getElementById('spacecom-receb-paginacao');
if (!container) return;
if (typeof showHistory === 'boolean') {
window.spacecomRecebimentosHistoricoAtivo = showHistory;
}
const modoHistorico = !!window.spacecomRecebimentosHistoricoAtivo;
const saidasTriagem = (window.dbData.movimentosManuais || []).filter(m => {
if (m.tipo !== 'Saída para Triagem/Manutenção') return false;
return !m.arquivado;
});
const pendentesLegado = saidasTriagem.filter(m => {
const isPendente = (!m.status || m.status === 'Pendente') && !m.arquivado;
if (!isPendente) return false;
return !(m.recibo && String(m.recibo).trim());
});
// No modo pendente, exibe somente recibos do fluxo atual (com número de recibo)
const recibos = saidasTriagem.filter(m => {
if (modoHistorico) return true;
return (!m.status || m.status === 'Pendente') && !m.arquivado;
}).filter(m => {
if (modoHistorico) return true;
return !!(m.recibo && String(m.recibo).trim());
});
if (statusEl) {
const prefixo = modoHistorico ? 'Modo Histórico' : 'Modo Pendente';
statusEl.textContent = `${prefixo}: ${recibos.length} registro(s) listado(s).`;
}
if (legacyAlertEl) {
if (!modoHistorico && pendentesLegado.length > 0) {
legacyAlertEl.classList.remove('hidden');
legacyAlertEl.innerHTML = `${pendentesLegado.length} registro(s) legado sem número de recibo foram removidos desta fila. Consulte em
Histórico de Recibos .`;
} else {
legacyAlertEl.classList.add('hidden');
legacyAlertEl.innerHTML = '';
}
}
if (recibos.length === 0) {
container.innerHTML = `
${modoHistorico ? 'Nenhum recibo no histórico.' : 'Nenhum recibo pendente no fluxo atual.'}
`;
if (paginacaoEl) paginacaoEl.innerHTML = '';
if (!window.spacecomRecebimentoAtivo || window.spacecomRecebimentoAtivo.manual) {
window.iniciarConferenciaManualSpacecom(false);
}
return;
}
const recibosMap = {};
recibos.forEach(m => {
const key = m.recibo || m.id;
if (!recibosMap[key]) {
recibosMap[key] = { id: key, data: m.data, responsavel: m.responsavel, itens: [], qtd: 0 };
}
recibosMap[key].qtd += parseInt(m.qtd || 0, 10);
recibosMap[key].itens.push(`${m.qtd || '?'}x ${m.item || 'Equipamento'}`);
});
const list = Object.values(recibosMap).reverse();
const porPagina = Math.max(1, parseInt(window.spacecomRecebimentosPorPagina || 5, 10));
const totalPaginas = Math.max(1, Math.ceil(list.length / porPagina));
let paginaAtual = Math.max(1, parseInt(window.spacecomRecebimentosPagina || 1, 10));
if (paginaAtual > totalPaginas) paginaAtual = totalPaginas;
window.spacecomRecebimentosPagina = paginaAtual;
const inicio = (paginaAtual - 1) * porPagina;
const paginaLista = list.slice(inicio, inicio + porPagina);
let html = '';
paginaLista.forEach(r => {
const dataFormatada = r.data ? r.data.split(' ')[0] : 'N/A';
html += `
${r.qtd || '?'} itens
${r.itens.join(', ')}
Enviado: ${r.responsavel || 'N/A'}
`;
});
container.innerHTML = html;
if (paginacaoEl) {
const btnVoltar = modoHistorico
? '
Voltar para Pendentes'
: '
Ver Histórico';
paginacaoEl.innerHTML = `
Página ${paginaAtual} de ${totalPaginas}
${btnVoltar}
Anterior
= totalPaginas ? 'disabled' : ''} class="btn-scan px-3 text-[10px] h-9 justify-center whitespace-nowrap ${paginaAtual >= totalPaginas ? 'opacity-40 cursor-not-allowed' : ''}">
Próxima
`;
}
if (!window.spacecomRecebimentoAtivo) {
window.iniciarConferenciaManualSpacecom(false);
}
lucide.createIcons();
};
window.selecionarRecebimentoSpacecom = function(reciboId) {
const allowHistory = !!window.spacecomRecebimentosHistoricoAtivo;
const recibos = (window.dbData.movimentosManuais || []).filter(m => {
if ((m.recibo || m.id) !== reciboId) return false;
if (m.tipo !== 'Saída para Triagem/Manutenção') return false;
if (allowHistory) return !m.arquivado;
return (!m.status || m.status === 'Pendente') && !m.arquivado;
});
window.spacecomRecebimentosHistoricoAtivo = false;
if (recibos.length === 0) {
window.showToast('Recibo não encontrado', 'error');
return;
}
const totalQty = recibos.reduce((sum, m) => sum + parseInt(m.qtd || 0, 10), 0);
const responsavel = recibos.find(m => m.responsavel)?.responsavel || recibos[0].responsavel || 'N/A';
const itemNames = Array.from(new Set(recibos.map(m => m.item))).join(', ');
const expectedText = recibos.map(r => r.seriais || r.serialsBipados || r.obs || '').filter(Boolean).join('\n').trim();
window.spacecomRecebimentoAtivo = {
id: reciboId,
recibo: reciboId,
responsavel,
qtd: totalQty,
totalQty,
item: itemNames || recibos[0].item,
obs: recibos.map(r => r.obs || '').filter(Boolean).join('\n'),
expectedSeriais: expectedText,
manual: false
};
window.spacecomRecebimentoOriginais = recibos;
window.spacecomRecebimentoValidados = [];
window.spacecomRecebimentoFalhas = [];
window.spacecomRecebimentoSobras = [];
window.spacecomRecebimentoPares = [];
const reciboInput = document.getElementById('spacecom-receb-manual-recibo');
const responsavelInput = document.getElementById('spacecom-receb-manual-responsavel');
const itemInput = document.getElementById('spacecom-receb-manual-item');
if (reciboInput) reciboInput.value = reciboId;
if (responsavelInput) responsavelInput.value = String(responsavel || '').toUpperCase();
if (itemInput && recibos[0]?.item) itemInput.value = String(recibos[0].item || '').toUpperCase();
// Preencher dados
document.getElementById('spacecom-receb-numero').textContent = reciboId;
document.getElementById('spacecom-receb-responsavel').textContent = responsavel;
const expectedInput = document.getElementById('spacecom-receb-esperados');
if (expectedInput) {
expectedInput.value = expectedText;
if (!expectedText) {
expectedInput.placeholder = `Equipamento: ${itemNames || recibos[0].item}\nQtd: ${totalQty}\nObs: ${window.spacecomRecebimentoAtivo.obs || 'Sem observação'}`;
}
}
// Limpar campos
document.getElementById('spacecom-receb-bipados').value = '';
const colabNomeEl = document.getElementById('spacecom-receb-colab-nome');
const colabCpfEl = document.getElementById('spacecom-receb-colab-cpf');
if (colabNomeEl) colabNomeEl.textContent = window.currentUser?.nome || 'NÃO INFORMADO';
if (colabCpfEl) colabCpfEl.textContent = window.currentUser?.cpf ? window.formatarCpf(window.currentUser.cpf) : 'CPF NÃO CADASTRADO';
document.getElementById('spacecom-receb-resultado').classList.add('hidden');
document.getElementById('btn-pdf-receb-spacecom').classList.add('hidden');
document.getElementById('btn-confirmar-receb-spacecom').classList.add('hidden');
// Atualizar contadores
window.atualizarContagemSeriais('spacecom-receb-bipados');
if (window.atualizarContagemSeriais) window.atualizarContagemSeriais('spacecom-receb-esperados');
// Focus no campo de bipar
setTimeout(() => document.getElementById('spacecom-receb-bipados').focus(), 300);
window.showToast(`Recibo ${reciboId} carregado para conferência`, 'success');
};
window.limparBipadosSpacecom = function() {
document.getElementById('spacecom-receb-bipados').value = '';
window.atualizarContagemSeriais('spacecom-receb-bipados');
};
window.gerarPDFDivergenciaRecebSpacecom = function() {
const pares = window.spacecomRecebimentoPares || [];
const faltantes = window.spacecomRecebimentoFalhas || [];
const sobras = window.spacecomRecebimentoSobras || [];
const reciboId = window.spacecomRecebimentoAtivo?.id || window.spacecomRecebimentoAtivo?.recibo || 'N/A';
let bD = [];
if (pares.length > 0) {
pares.forEach(p => bD.push(['PAREAMENTO PROVÁVEL', p.bipado, p.recibo, 'Possível erro de numeração no recibo']));
}
faltantes.forEach(s => bD.push(['FALTANTE', '-', s, 'Consta no recibo, mas não foi bipado']));
sobras.forEach(s => bD.push(['SOBRA', s, '-', 'Bipado, mas não consta no recibo']));
if (bD.length === 0) {
window.showToast('Nenhuma divergência para gerar relatório.', 'warning');
return;
}
window.gerarPdfUniversal({
tipo: 'tabela_relatorio', orientacao: 'portrait',
titulo: 'RELATÓRIO DE INCONSISTÊNCIA DE ESTOQUE - SPACECOM',
subtitulo: `Recibo: ${reciboId} | Data: ${new Date().toLocaleString('pt-BR')} | Responsável: ${currentUser.nome} (${currentUser.masp})`,
head: ['Tipo', 'Leitura Bipada (Físico)', 'Número no Recibo', 'Observação'],
body: bD,
nomeArquivo: `Relatorio_Inconsistencia_Spacecom_${new Date().getTime()}.pdf`
});
};
window.cancelarRecebimentoSpacecom = function() {
const reciboAtivo = window.spacecomRecebimentoAtivo;
const user = window.currentUser || currentUser || {};
const isGestor = ['admin', 'central', 'nucleo'].includes(String(user.role || ''));
if (!reciboAtivo) {
if (confirm('Deseja limpar a conferência atual e manter o painel pronto para novo recibo físico?')) {
window.iniciarConferenciaManualSpacecom(false);
window.carregarRecebimentosSpacecom();
}
return;
}
const reciboId = reciboAtivo.id || reciboAtivo.recibo;
const original = (window.dbData.movimentosManuais || []).filter(m => (m.recibo || m.id) === reciboId && m.tipo === 'Saída para Triagem/Manutenção');
const possuiProcessamento = original.some(m => ['Recebido pela Spacecom', 'Em Inspeção Spacecom', 'Em Triagem', 'Devolvido ao Policial'].includes(m.status));
// Usuário comum cancela apenas a edição local da tela (sem rollback de fluxo)
if (!isGestor) {
if (confirm('Deseja limpar a edição atual e voltar ao modo de conferência manual?')) {
window.spacecomRecebimentoAtivo = null;
window.spacecomRecebimentoValidados = [];
window.iniciarConferenciaManualSpacecom(false);
window.carregarRecebimentosSpacecom();
window.showToast('Ação local cancelada.', 'success');
}
return;
}
// Gestor pode cancelar o recibo e reverter tudo para fora da Spacecom
const msgGestor = possuiProcessamento
? `Cancelar o recibo ${reciboId}?\n\nAtenção: os itens serão removidos da inspeção Spacecom e o recibo voltará para PENDENTE.`
: `Deseja apenas fechar a conferência do recibo ${reciboId}?`;
if (!confirm(msgGestor)) return;
if (possuiProcessamento) {
let alterados = 0;
let removidos = 0;
const reciboRef = String(reciboId || '').trim();
window.dbData.movimentosManuais = (window.dbData.movimentosManuais || []).filter(m => {
const ehRecebimentoVinculado = m.tipo === 'Recebimento Spacecom' && (
String(m.reciboOriginal || '').trim() === reciboRef ||
String(m.recibo || '').trim() === `REC-${reciboRef}` ||
String(m.obs || '').includes(`Original: ${reciboRef}`)
);
const ehSaidaPolicialVinculada = m.tipo === 'Saída Spacecom para Policial' && String(m.reciboOriginal || '').trim() === reciboRef;
if (ehRecebimentoVinculado || ehSaidaPolicialVinculada) {
removidos++;
return false;
}
return true;
});
(window.dbData.movimentosManuais || []).forEach(m => {
if ((m.recibo || m.id) === reciboId && m.tipo === 'Saída para Triagem/Manutenção') {
m.status = 'Pendente';
delete m.recebidoData;
delete m.recebidoPor;
delete m.recebidoPorCpf;
delete m.aceiteSpacecom;
delete m.devolucaoData;
delete m.devolucaoPor;
delete m.triagemdoConcluida;
delete m.triademPor;
delete m.equipamentoTriados;
alterados++;
}
});
window.limparReferenciasCanceladas?.();
saveToCloud(['movimentosManuais']).then((ok) => {
if (!ok) {
window.showToast('Falha ao salvar o cancelamento na nuvem.', 'error');
return;
}
window.spacecomRecebimentoAtivo = null;
window.spacecomRecebimentoValidados = [];
window.iniciarConferenciaManualSpacecom(false);
window.carregarRecebimentosSpacecom();
if (typeof window.carregarRecibosSpacecom === 'function') window.carregarRecibosSpacecom();
if (typeof window.renderSpacecomInspecaoCards === 'function') window.renderSpacecomInspecaoCards();
window.showToast(`Recibo cancelado. ${alterados} registro(s) revertido(s) e ${removidos} movimentação(ões) removida(s).`, 'success');
}).catch(() => {
window.showToast('Erro ao persistir cancelamento do recibo.', 'error');
});
return;
}
window.spacecomRecebimentoAtivo = null;
window.spacecomRecebimentoValidados = [];
window.iniciarConferenciaManualSpacecom(false);
window.carregarRecebimentosSpacecom();
window.showToast('Conferência fechada sem alterações.', 'success');
};
window.verificarRecebimentoSpacecom = function() {
const resDiv = document.getElementById('spacecom-receb-resultado');
const btnConf = document.getElementById('btn-confirmar-receb-spacecom');
const btnPdf = document.getElementById('btn-pdf-receb-spacecom');
if (!window.spacecomRecebimentoAtivo) {
window.iniciarConferenciaManualSpacecom(false);
}
const reciboAtivo = window.spacecomRecebimentoAtivo || window.montarRecebimentoManualSpacecom();
const esperadosText = (document.getElementById('spacecom-receb-esperados')?.value || reciboAtivo?.expectedSeriais || reciboAtivo?.obs || '').trim();
const bipadosText = document.getElementById('spacecom-receb-bipados').value;
if (!bipadosText.trim()) {
window.showToast('Bipar equipamentos primeiro', 'error');
return;
}
// Expandir seriais e normalizar
const listaBipados = expandirSeriais(bipadosText).filter(s => s !== "Nenhum serial informado / Range omitido");
const expectedList = expandirSeriais(esperadosText).filter(s => s !== "Nenhum serial informado / Range omitido");
const expectedSet = new Set(expectedList.map(s => s.replace(/^0+/, '') || '0'));
const bipadosSet = new Set(listaBipados.map(s => s.replace(/^0+/, '') || '0'));
const missing = expectedList.filter(s => !bipadosSet.has(s.replace(/^0+/, '') || '0'));
const extra = listaBipados.filter(s => !expectedSet.has(s.replace(/^0+/, '') || '0'));
const paresProvaveis = window.calcularPareamentosProvaveisSeriais(extra, missing, 2);
window.spacecomRecebimentoFalhas = missing;
window.spacecomRecebimentoSobras = extra;
window.spacecomRecebimentoPares = paresProvaveis;
const qtdEsperada = parseInt(reciboAtivo.qtd || 0, 10) > 0
? parseInt(reciboAtivo.qtd || 0, 10)
: (expectedList.length > 0 ? expectedList.length : listaBipados.length);
window.spacecomRecebimentoAtivo.qtd = qtdEsperada;
window.spacecomRecebimentoAtivo.totalQty = qtdEsperada;
// Mostrar resultado
resDiv.classList.remove('hidden');
let html = '';
if (listaBipados.length === 0) {
resDiv.className = 'p-4 rounded-lg text-xs font-mono whitespace-pre-wrap max-h-[200px] overflow-y-auto custom-scroll border border-red-500 bg-red-900/20 text-red-400 leading-relaxed';
html = `🚨 Nenhum serial válido foi bipado.\nTente novamente.`;
btnConf.classList.add('hidden');
btnPdf.classList.add('hidden');
} else {
resDiv.className = 'p-4 rounded-lg text-xs font-mono whitespace-pre-wrap max-h-[200px] overflow-y-auto custom-scroll border border-green-500 bg-green-900/20 text-green-400 leading-relaxed';
html = `✅ CONFERÊNCIA REALIZADA\n\n`;
html += `Total Bipado: ${listaBipados.length} equipamento(s)\n`;
html += `Quantidade Esperada: ${qtdEsperada || '?'}\n\n`;
if (expectedList.length > 0) {
html += `Seriais esperados: ${expectedList.length} \n`;
if (missing.length === 0) {
html += `Seriais faltantes: 0\n`;
} else {
html += `Seriais faltantes: ${missing.length}\n`;
}
if (extra.length > 0) {
html += `Seriais inesperados: ${extra.length}\n`;
}
if (paresProvaveis.length > 0) {
html += `\nRELATÓRIO DE INCONSISTÊNCIA DE NUMERAÇÃO (PROVÁVEL):\n`;
paresProvaveis.slice(0, 10).forEach(p => {
html += `- Bipado (Físico): ${p.bipado} | Consta no Recibo: ${p.recibo}\n`;
});
}
html += '\n';
}
if (listaBipados.length === qtdEsperada && missing.length === 0) {
html += `Status: ✅ QUANTIDADE E SERIAIS COMPATÍVEIS - Pronto para confirmar`;
window.spacecomRecebimentoValidados = listaBipados;
btnConf.classList.remove('hidden');
btnPdf.classList.remove('hidden');
} else if (listaBipados.length < qtdEsperada) {
html += `Status: ⚠️ FALTAM ${qtdEsperada - listaBipados.length} EQUIPAMENTO(S)`;
html += `\nBipe os equipamentos restantes para completar o recebimento.`;
if (missing.length > 0) html += `\nFaltam também ${missing.length} serial(ais) esperados.`;
window.spacecomRecebimentoValidados = listaBipados;
btnConf.classList.remove('hidden');
btnPdf.classList.add('hidden');
} else {
html += `Status: ⚠️ SOBRAM ${listaBipados.length - qtdEsperada} EQUIPAMENTO(S)`;
if (extra.length > 0) html += `\n${extra.length} serial(ais) não esperados detectados.`;
html += `\nRevise os equipamentos bipados.`;
window.spacecomRecebimentoValidados = listaBipados;
btnConf.classList.remove('hidden');
btnPdf.classList.add('hidden');
}
if (missing.length > 0 || extra.length > 0 || paresProvaveis.length > 0) {
html += `\n\n
Baixar Relatório de Inconsistência`;
}
}
resDiv.innerHTML = html;
lucide.createIcons();
};
window.confirmarRecebimentoSpacecom = function() {
if (!window.spacecomRecebimentoAtivo) {
window.iniciarConferenciaManualSpacecom(false);
}
const recibo = window.spacecomRecebimentoAtivo;
const bipados = window.spacecomRecebimentoValidados;
if (!bipados || bipados.length === 0) {
window.showToast('Verifique e valide os seriais antes de confirmar o recebimento.', 'error');
return;
}
const user = window.currentUser || currentUser;
if (!user) {
window.showToast('Usuário logado não identificado. Faça login novamente.', 'error');
return;
}
const recebidoData = new Date().toLocaleString('pt-BR');
const recebidoPor = user.nome || user.nomeColaborador || 'NÃO INFORMADO';
const cpfInput = String(user?.cpf || '').replace(/\D/g, '');
if (cpfInput.length !== 11) {
window.showToast('CPF do colaborador não encontrado no cadastro do usuário logado.', 'error');
return;
}
const serialsText = bipados.join('\n');
const isManual = !!recibo.manual;
const manualItem = String(document.getElementById('spacecom-receb-manual-item')?.value || recibo.item || 'TZPR').toUpperCase();
const manualResponsavel = (document.getElementById('spacecom-receb-manual-responsavel')?.value || recibo.responsavel || recebidoPor || 'RECIBO FÍSICO').trim();
const manualReciboId = (document.getElementById('spacecom-receb-manual-recibo')?.value || recibo.id || window.criarReciboManualSpacecom()).trim();
if (!isManual) {
// Atualizar todos os movimentos originais do recibo
(window.dbData.movimentosManuais || []).forEach(m => {
if ((m.recibo || m.id) === recibo.id && m.tipo === 'Saída para Triagem/Manutenção') {
m.status = 'Em Inspeção Spacecom';
m.recebidoData = recebidoData;
m.recebidoPor = recebidoPor;
m.recebidoPorCpf = cpfInput;
m.seriais = serialsText;
m.serialsBipados = serialsText;
m.aceiteSpacecom = {
nome: recebidoPor,
cpf: cpfInput,
data: recebidoData
};
m.historicoSpacecom = Array.isArray(m.historicoSpacecom) ? m.historicoSpacecom : [];
m.historicoSpacecom.push({
acao: 'Recebimento confirmado pela Spacecom',
data: recebidoData,
por: recebidoPor,
cpf: cpfInput
});
m.obs = `${String(m.obs || '').trim()} | Ação Spacecom: Recebido por ${recebidoPor} em ${recebidoData}`.trim();
}
});
} else {
// Recibo físico/manual: cria um recibo de entrada em inspeção para alimentar a triagem.
window.dbData.movimentosManuais.push({
id: 'MAN-' + Date.now().toString().slice(-8) + Math.floor(Math.random() * 100),
data: recebidoData,
item: manualItem,
tipo: 'Saída para Triagem/Manutenção',
qtd: bipados.length,
obs: `Recibo físico importado e conferido na Spacecom.`,
responsavel: manualResponsavel,
recibo: manualReciboId,
status: 'Em Inspeção Spacecom',
unidade: 'Spacecom',
seriais: serialsText,
serialsBipados: serialsText,
recebidoData,
recebidoPor,
recebidoPorCpf: cpfInput,
origem: 'Recibo Físico',
motivo: 'RECUPERADO'
});
}
// Adicionar movimento de recebimento
window.dbData.movimentosManuais.push({
id: 'REC-' + Date.now().toString().slice(-6) + Math.floor(Math.random() * 100),
data: recebidoData,
item: isManual ? manualItem : recibo.item,
tipo: 'Recebimento Spacecom',
qtd: bipados.length,
obs: `Recebimento de equipamentos para triagem. Original: ${isManual ? manualReciboId : (recibo.recibo || recibo.id)}. Equipamentos: ${bipados.join(', ').substring(0, 100)}...`,
responsavel: recebidoPor,
cpf: cpfInput,
recibo: 'REC-' + (isManual ? manualReciboId : recibo.id),
status: 'Em Inspeção Spacecom',
unidade: 'Spacecom',
seriais: serialsText,
serialsBipados: serialsText,
reciboOriginal: isManual ? manualReciboId : recibo.id
});
// Salvar na nuvem
saveToCloud(['movimentosManuais']).then((ok) => {
if (!ok) {
window.showToast('Falha ao confirmar recebimento na nuvem.', 'error');
return;
}
window.spacecomRecebimentoAtivo = null;
window.spacecomRecebimentoValidados = [];
window.iniciarConferenciaManualSpacecom(false);
window.carregarRecebimentosSpacecom();
window.carregarRecibosSpacecom();
window.renderSpacecomInspecaoCards();
window.showToast('Recebimento confirmado com aceite (nome + CPF).', 'success');
}).catch(() => {
window.showToast('Erro ao confirmar recebimento.', 'error');
});
};
// Se a aba Spacecom já estiver ativa quando este bloco carregar, inicializa os dropdowns imediatamente.
if (window.currentUser && window.currentUser.role === 'spacecom') {
const triagemAtiva = document.getElementById('spacecom-triagem')?.classList.contains('active');
const recebAtivo = document.getElementById('spacecom-recebimento')?.classList.contains('active');
if (triagemAtiva && typeof window.initSpacecomItems === 'function') window.initSpacecomItems();
if (recebAtivo && typeof window.carregarRecebimentosSpacecom === 'function') {
window.carregarRecebimentosSpacecom();
window.iniciarConferenciaManualSpacecom(false);
}
}
}, 2500);
/* --- FIM PATCH SPACECOM --- */
console.log("SIGME V13 ESTADUAL: Layout inicial modernizado, responsividade ampliada e base preparada para operação estadual.");
console.log("SIGME V13.1 ESTADUAL: Tipografia refinada, navegação aprimorada e legibilidade global de tabelas e cards.");
// ============================================================================
// INICIALIZAÇÃO DO SISTEMA DE IA - APÓS TODAS AS FUNÇÕES SEREM DEFINIDAS
// ============================================================================
setTimeout(() => {
if (typeof window.atualizarSugestoesPerguntas === 'function') {
window.atualizarSugestoesPerguntas();
console.log('✅ Sistema de IA inicializado com sucesso');
}
}, 500);
// V13.5: IA com atualização inteligente (event-driven) + fallback leve
(function initV135IAUpdates() {
let iaDebounce = null;
const runIAUpdate = () => {
if (typeof window.atualizarSugestoesPerguntas === 'function') {
window.atualizarSugestoesPerguntas();
}
};
const scheduleIAUpdate = (delay = 120) => {
clearTimeout(iaDebounce);
iaDebounce = setTimeout(runIAUpdate, delay);
};
// Atualiza ao voltar para a aba/janela
document.addEventListener('visibilitychange', () => {
if (!document.hidden) scheduleIAUpdate(80);
});
window.addEventListener('focus', () => scheduleIAUpdate(100));
// Atualiza ao navegar no menu lateral
const nav = document.getElementById('nav-menu-sidebar');
if (nav) {
nav.addEventListener('click', () => scheduleIAUpdate(90));
}
// Atualiza quando qualquer módulo sinaliza troca de aba
document.addEventListener('sigme:tab-changed', () => scheduleIAUpdate(70));
// Fallback de baixa frequência, apenas quando a aba estiver visível
setInterval(() => {
if (!document.hidden) runIAUpdate();
}, 15000);
setTimeout(runIAUpdate, 420);
console.log('SIGME V13.5 ESTADUAL: atualização de IA otimizada com gatilhos e fallback leve.');
})();
// ============================================================================
// V13.2 - UX OPERACIONAL (ATALHOS + DENSIDADE)
// ============================================================================
(function initV132OperationalUX() {
const densityKey = 'sigme-density';
const savedDensity = localStorage.getItem(densityKey);
if (savedDensity === 'compact') document.body.setAttribute('data-density', 'compact');
const setDensity = (mode) => {
if (mode === 'compact') {
document.body.setAttribute('data-density', 'compact');
localStorage.setItem(densityKey, 'compact');
if (window.showToast) window.showToast('Modo compacto ativado (V13.2).', 'success');
} else {
document.body.removeAttribute('data-density');
localStorage.setItem(densityKey, 'normal');
if (window.showToast) window.showToast('Modo compacto desativado.', 'success');
}
};
const toggleDensity = () => {
const current = document.body.getAttribute('data-density') === 'compact' ? 'compact' : 'normal';
setDensity(current === 'compact' ? 'normal' : 'compact');
};
document.addEventListener('keydown', (ev) => {
// Atalhos operacionais: Alt+1..6 (abas) e Alt+D (densidade)
if (!ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) return;
const map = {
'1': 'dashboard',
'2': 'logistica',
'3': 'devolucoes',
'4': 'utilizados',
'5': 'relatorio',
'6': 'plantao'
};
if (map[ev.key] && typeof window.switchTab === 'function') {
ev.preventDefault();
window.switchTab(map[ev.key]);
return;
}
if (ev.key.toLowerCase() === 'd') {
ev.preventDefault();
toggleDensity();
}
});
console.log('SIGME V13.2 ESTADUAL: UX operacional ativada (atalhos e modo compacto).');
})();
// ============================================================================
// V13.3 - PRODUTIVIDADE (AÇÕES POR LINHA + FILTRO PERSISTENTE + DESTAQUES)
// ============================================================================
(function initV133Productivity() {
const FILTER_PREFIX = 'sigme-v133-filter-';
let refreshTimer = null;
let activeTabObserver = null;
const normalize = (txt) => String(txt || '')
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
const activeTab = () => document.querySelector('.tab-content.active');
const highlightByRisk = (container) => {
if (!container) return;
const targets = container.querySelectorAll('tbody tr, .card-panel');
targets.forEach((el) => {
el.classList.remove('v133-critico', 'v133-pendente');
const text = normalize(el.textContent);
if (/(critico|crítico|ruptura|urgente)/.test(text)) {
el.classList.add('v133-critico');
} else if (/(pendente|atencao|atenção|aguardando)/.test(text)) {
el.classList.add('v133-pendente');
}
});
};
const copyText = async (value) => {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(value);
} else {
const ta = document.createElement('textarea');
ta.value = value;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
if (window.showToast) window.showToast('Linha copiada.', 'success');
} catch {
if (window.showToast) window.showToast('Não foi possível copiar a linha.', 'error');
}
};
const ensureRowActions = (tab) => {
if (!tab) return;
tab.querySelectorAll('table').forEach((table) => {
const headRow = table.querySelector('thead tr');
const bodyRows = table.querySelectorAll('tbody tr');
if (!headRow || bodyRows.length === 0) return;
if (!headRow.querySelector('th[data-v133-action-head]')) {
const th = document.createElement('th');
th.className = 'th-std text-center';
th.dataset.v133ActionHead = '1';
th.textContent = 'Ações';
headRow.insertBefore(th, headRow.firstElementChild);
}
bodyRows.forEach((row) => {
if (row.querySelector('td[data-v133-action-cell]')) return;
const firstCell = row.firstElementChild;
const td = document.createElement('td');
td.className = 'td-std v133-action-cell';
td.dataset.v133ActionCell = '1';
// Em linhas-resumo com colspan, evita quebrar o layout visual.
if (firstCell && firstCell.hasAttribute('colspan')) {
td.innerHTML = '
- ';
row.insertBefore(td, row.firstElementChild);
return;
}
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'v133-action-btn';
btn.textContent = 'Copiar';
btn.addEventListener('click', async () => {
const rowText = Array.from(row.children)
.filter((c) => !c.hasAttribute('data-v133-action-cell'))
.map((c) => c.innerText.trim())
.join(' | ');
await copyText(rowText);
});
td.appendChild(btn);
row.insertBefore(td, row.firstElementChild);
});
});
};
const applyTabFilter = (tab) => {
if (!tab) return;
const input = tab.querySelector('.v133-filter-input');
const query = normalize(input ? input.value : '');
tab.querySelectorAll('tbody tr').forEach((row) => {
const txt = normalize(row.textContent);
row.style.display = (!query || txt.includes(query)) ? '' : 'none';
});
};
const ensureFilterBar = (tab) => {
if (!tab || !tab.id) return;
if (tab.querySelector('.v133-filter-bar')) return;
const hasTable = tab.querySelector('table');
if (!hasTable) return;
const wrap = document.createElement('div');
wrap.className = 'v133-filter-bar';
wrap.innerHTML = `
Limpar
`;
tab.insertBefore(wrap, tab.firstElementChild.nextElementSibling || tab.firstElementChild);
const input = wrap.querySelector('.v133-filter-input');
const clear = wrap.querySelector('.v133-filter-btn');
const key = FILTER_PREFIX + tab.id;
input.value = localStorage.getItem(key) || '';
input.addEventListener('input', () => {
localStorage.setItem(key, input.value);
applyTabFilter(tab);
});
clear.addEventListener('click', () => {
input.value = '';
localStorage.setItem(key, '');
applyTabFilter(tab);
});
};
const refreshProductivity = () => {
const tab = activeTab();
if (!tab) return;
ensureFilterBar(tab);
ensureRowActions(tab);
applyTabFilter(tab);
highlightByRisk(tab);
};
const scheduleRefresh = (delay = 80) => {
clearTimeout(refreshTimer);
refreshTimer = setTimeout(refreshProductivity, delay);
};
const observeActiveTab = () => {
const tab = activeTab();
if (!tab) return;
if (activeTabObserver) {
activeTabObserver.disconnect();
activeTabObserver = null;
}
activeTabObserver = new MutationObserver(() => scheduleRefresh(90));
activeTabObserver.observe(tab, { childList: true, subtree: true });
};
document.addEventListener('keydown', (ev) => {
if (!ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) return;
if (ev.key.toLowerCase() === 'f') {
const tab = activeTab();
const input = tab ? tab.querySelector('.v133-filter-input') : null;
if (input) {
ev.preventDefault();
input.focus();
input.select();
}
}
});
if (typeof window.switchTab === 'function' && !window._v133SwitchHooked) {
const originalSwitchTab = window.switchTab;
window.switchTab = function(tabId) {
const out = originalSwitchTab.call(this, tabId);
scheduleRefresh(60);
setTimeout(observeActiveTab, 120);
document.dispatchEvent(new CustomEvent('sigme:tab-changed', { detail: { tabId } }));
return out;
};
window._v133SwitchHooked = true;
}
const nav = document.getElementById('nav-menu-sidebar');
if (nav) nav.addEventListener('click', () => { scheduleRefresh(120); setTimeout(observeActiveTab, 150); });
setTimeout(() => {
refreshProductivity();
observeActiveTab();
}, 450);
console.log('SIGME V13.3 ESTADUAL: produtividade ativada (ações por linha, filtro persistente e destaque automático).');
})();
console.log('SIGME V13.4 ESTADUAL: polling reduzido e atualização inteligente de UI habilitada.');