/* global React, Icon, BRL, NUM, Ticker, Drawer */ function MLogistica({ onNav }) { const [postagens, setPostagens] = React.useState([]); const [triangulacoes, setTriangulacoes] = React.useState([]); const [loading, setLoading] = React.useState(true); React.useEffect(() => { let mounted = true; Promise.all([ window.dataApi.listPostagens(), window.dataApi.listTriangulacoes(), ]).then(([p, t]) => { if (!mounted) return; setPostagens(p); setTriangulacoes(t); setLoading(false); }).catch(() => { if (mounted) setLoading(false); }); return () => { mounted = false; }; }, []); const [postagem, setPostagem] = React.useState(null); const [postagemTab, setPostagemTab] = React.useState('rastreio'); const [triang, setTriang] = React.useState(null); const [triangTab, setTriangTab] = React.useState('det'); const [updating, setUpdating] = React.useState(false); const [toast, setToast] = React.useState(null); const [prePost, setPrePost] = React.useState({ empenho: '2026NE000847', peso: '12,50 kg', volumes: '4', modal: 'SEDEX' }); const [gerandoEtiqueta, setGerandoEtiqueta] = React.useState(false); const [showColetaReversa, setShowColetaReversa] = React.useState(false); const showToast = (msg, ms = 4000) => { setToast(msg); setTimeout(() => setToast(null), ms); }; const handleUpdateRastreios = () => { if (updating) return; setUpdating(true); setTimeout(() => { setUpdating(false); const atualizadas = Math.floor(Math.random() * 4) + 2; showToast(`${postagens.length} postagens consultadas · ${atualizadas} ${atualizadas === 1 ? 'rastreio atualizado' : 'rastreios atualizados'} · Correios + SSW`); }, 1800); }; const handleNovaPrePostagem = () => { // Foca no campo de empenho do card lateral showToast('Use o card "Pré-postagem rápida" ao lado para gerar a etiqueta. Preencha os dados e clique em "Gerar etiqueta + código".'); setTimeout(() => { const el = document.querySelector('#pre-post-empenho'); if (el) { el.focus(); el.select(); } }, 100); }; const setP = (k, v) => setPrePost({ ...prePost, [k]: v }); const handleGerarEtiqueta = () => { if (gerandoEtiqueta) return; if (!prePost.empenho) { showToast('Informe o número do empenho antes de gerar a etiqueta'); return; } setGerandoEtiqueta(true); setTimeout(() => { setGerandoEtiqueta(false); const cod = 'BR' + String(Math.floor(Math.random() * 9000000) + 1000000) + 'BR'; showToast(`Etiqueta ${cod} gerada · ${prePost.modal} · ${prePost.empenho} · pronta para impressão`); }, 1500); }; return ( <>
Gestão Logística e TriangulaçãoM06

Logística · Correios · SSW · Triangulação

Pré-postagem, etiqueta, rastreio, comprovante de entrega e Logística Reversa. Triangulação separada do estoque físico — não passa pela gestão de estoque.

!['entregue'].includes(p.status)).length} sub="rastreio ativo" cls="cyan" /> p.reverso).length} sub="aguardando coleta" cls="amber" />
Postagens · {postagens.length} ativas
clique em uma linha para detalhe
{postagens.map(t => ( { setPostagem(t); setPostagemTab('rastreio'); }} style={{ cursor: 'pointer' }}> ))}
Rastreio Empenho Destino Modal Postado Status
{t.rastreio} {t.empenho} {t.dest} {t.modal} {t.postado} {t.statusLabel}
Pré-postagem rápida
EMPENHO
setP('empenho', e.target.value)} placeholder="2026NE000XXX" />
DESTINATÁRIO (auto do empenho)
Prefeitura Mun. de Niterói
R. V. de Sepetiba, 987 · 24020-200
PESO
setP('peso', e.target.value)} />
VOLUMES
setP('volumes', e.target.value)} />
MODAL
{['SEDEX', 'PAC', 'JADLOG', 'SSW'].map(m => (
setP('modal', m)}>{m}
))}
Frete CIF cotado ({prePost.modal}): R$ {prePost.modal === 'SEDEX' ? '312,40' : prePost.modal === 'PAC' ? '198,60' : prePost.modal === 'JADLOG' ? '254,80' : '218,40'} · entrega prevista {prePost.modal === 'PAC' ? '5' : prePost.modal === 'JADLOG' ? '4' : '3'} dias úteis
Logística Reversa
{postagens.filter(p => p.reverso).map(p => (
{ setPostagem(p); setPostagemTab('rastreio'); }}>
{p.empenho} · {p.motivoReverso?.split('.')[0]}.
{p.rastreio} · clique para detalhes
))}
Triangulação · {triangulacoes.length} ativas
clique para detalhe
{triangulacoes.map(t => (
{ setTriang(t); setTriangTab('det'); }} style={{ display: 'grid', gridTemplateColumns: '1fr 80px', gap: 8, padding: '8px 10px', background: 'var(--bg-2)', borderRadius: 4, borderLeft: t.inner ? '2px solid var(--amber)' : '2px solid var(--lime)', }}>
{t.id} · {t.pedidoCompra}
{t.forn} → {t.dest}
{t.inner &&
⚠ INNER · +{t.sobra} un sobra
}
{BRL(t.val).replace('R$ ', '')}
))}
Triangulação não passa pelo estoque físico — mantém integridade.
{postagem && setPostagem(null)} tab={postagemTab} onTab={setPostagemTab} onNav={onNav} />} {triang && setTriang(null)} tab={triangTab} onTab={setTriangTab} onNav={onNav} />} {showColetaReversa && setShowColetaReversa(false)} onAgendar={(emp) => { setShowColetaReversa(false); showToast(`Coleta reversa agendada para ${emp} · Correios notificado · prazo de coleta 2-3 dias úteis`); }} />} {toast && (
{toast}
)} ); } function ColetaReversaModal({ onClose, onAgendar }) { const [form, setForm] = React.useState({ empenho: '', motivo: 'defeito', qtd: '', descricao: '', enderecoColeta: '', }); const [erro, setErro] = React.useState(null); const set = (k, v) => setForm({ ...form, [k]: v }); const submit = () => { if (!form.empenho || !form.qtd || !form.enderecoColeta) { setErro('Preencha Empenho, Quantidade e Endereço de coleta'); return; } onAgendar(form.empenho); }; return (
e.stopPropagation()}>
Agendar coleta reversa
Devolução pelo cliente
Solicita coleta dos Correios no endereço do cliente. A mercadoria voltará para o almoxarifado da BTM em 2-3 dias úteis. Use para devoluções por defeito, troca ou divergência.
EMPENHO ORIGEM *
set('empenho', e.target.value)} placeholder="2026NE000XXX" />
QTD A COLETAR *
set('qtd', e.target.value)} placeholder="12" />
MOTIVO
{[ { id: 'defeito', l: 'DEFEITO' }, { id: 'troca', l: 'TROCA' }, { id: 'diverg', l: 'DIVERGÊNCIA' }, { id: 'cancel', l: 'CANCELAMENTO' }, ].map(o => (
set('motivo', o.id)}>{o.l}
))}
DESCRIÇÃO DO PROBLEMA