// SATMELI — Shell de la app: navegación + router por instancia. function BrandMark({ size = 30, withName = true, mode }) { return (
{withName && (
SATMELI
ALERTA TEMPRANA
)}
); } function ThemeBtn({ dark, onToggle }) { return ( ); } function App({ mode, dark, cardVariant, onToggleTheme, store, user }) { const { searches, notifs, toggleSearch, markRead, markAll, saveSearch, deleteSearch } = store; const [tab, setTab] = React.useState('dashboard'); const [overlay, setOverlay] = React.useState(null); // {type, id} const desktop = mode === 'desktop'; const unread = notifs.filter((n) => !n.read).length; const scrollRef = React.useRef(null); // ── Estado de conexiones (MELI + Telegram) ── const [meliStatus, setMeliStatus] = React.useState(null); const [tgStatus, setTgStatus] = React.useState(null); React.useEffect(() => { apiFetch('/meli/status').then(setMeliStatus).catch(() => setMeliStatus(null)); apiFetch('/telegram/status').then(setTgStatus).catch(() => setTgStatus(null)); }, []); // Solo el admin (flashiando@gmail.com) puede configurar conexiones; el resto solo ve el estado. const isAdmin = !!(user && user.isAdmin); const go = (t) => { setOverlay(null); setTab(t); if (scrollRef.current) scrollRef.current.scrollTop = 0; }; const openDetail = (id) => { setOverlay({ type: 'detail', id }); if (scrollRef.current) scrollRef.current.scrollTop = 0; }; const openCreate = () => { setOverlay({ type: 'create' }); if (scrollRef.current) scrollRef.current.scrollTop = 0; }; const openEdit = (id) => { setOverlay({ type: 'edit', id }); if (scrollRef.current) scrollRef.current.scrollTop = 0; }; const goMeli = () => window.open('https://www.mercadolibre.com.ar', '_blank'); const current = overlay ? null : tab; const selSearch = overlay ? searches.find((s) => s.id === overlay.id) : null; // ── pantallas ── let screen; if (overlay && overlay.type === 'detail' && selSearch) { screen = setOverlay(null)} onEdit={() => openEdit(selSearch.id)} onToggle={() => toggleSearch(selSearch.id)} onDelete={() => { deleteSearch(selSearch.id); setOverlay(null); }} onGoMeli={goMeli} />; } else if (overlay && (overlay.type === 'create' || overlay.type === 'edit')) { screen = setOverlay(null)} onSave={(s) => { saveSearch(s); setOverlay(null); setTab('dashboard'); }} onOpenTelegram={() => { setOverlay(null); setTab('telegram'); }} />; } else if (tab === 'dashboard') { screen = ; } else if (tab === 'feed') { screen = go('telegram')} />; } const isTelegram = !overlay && tab === 'telegram'; // ── NAV: tabs mobile ── const navItems = [ { id: 'dashboard', icon: 'grid', label: 'Panel' }, { id: 'feed', icon: 'bell', label: 'Alertas', badge: unread }, { id: 'telegram', icon: 'send', label: 'Telegram' }, ]; const bottomBar = (
go('dashboard')} /> go('feed')} /> go('telegram')} /> {}} />
); // ── MOBILE LAYOUT ── if (!desktop) { return (
{isTelegram ? (
go('dashboard')} notifs={notifs} searches={searches} />
) : ( <> {/* top bar */}
{!overlay && tab === 'dashboard' && (
)} {screen}
)} {!isTelegram && bottomBar}
); } // ── DESKTOP LAYOUT ── return (
{/* sidebar */}
{navItems.map((it) => ( go(it.id)} /> ))}
Nueva búsqueda
{user ? user.name : 'Usuario'}
{/* main */}
{isTelegram ? (
go('dashboard')} notifs={notifs} searches={searches} />
) : (
{screen}
)}
); } function TabBtn({ item, active, onClick }) { return ( ); } function SideBtn({ item, active, onClick }) { return ( ); } function UserAvatar({ user, size = 36 }) { if (user && user.picture) { return {user.name; } const initials = user && user.name ? user.name.charAt(0).toUpperCase() : 'U'; return (
{initials}
); } // ── Panel de conexiones (MELI + Telegram) — siempre visible ─────────────────── function connBtnStyle(border, color) { return { width: '100%', marginTop: 9, padding: '7px 8px', borderRadius: 8, border: '1px solid ' + border, background: 'transparent', color, fontFamily: 'var(--font)', fontSize: 11.5, fontWeight: 600, cursor: 'pointer' }; } function ConnIconML({ size = 22 }) { // Marca de Mercado Libre: cuadrito amarillo con "ML" (sin depender de un ícono externo). return ( ML ); } function ServiceRow({ mark, name, dot, statusText, action }) { return (
{mark} {name}
{statusText}
{action}
); } function ConnectionsPanel({ meliStatus, tgStatus, isAdmin }) { // ── Mercado Libre ── const meliLoading = meliStatus === null; const meliConnected = !!(meliStatus && meliStatus.connected); const meliConfigured = !!(meliStatus && meliStatus.configured); const meliDot = meliLoading ? 'var(--muted-3)' : meliConnected ? 'var(--signal)' : 'var(--alert)'; const meliText = meliLoading ? 'Verificando…' : meliConnected ? ('Conectado' + (isAdmin && meliStatus.userId ? ' · #' + meliStatus.userId : '')) : meliConfigured ? 'No conectado' : 'Sin configurar'; let meliAction = null; if (isAdmin && !meliLoading && meliConfigured && !meliConnected) { meliAction = ( ); } else if (isAdmin && meliConnected) { meliAction = ( ); } // ── Telegram ── const tgLoading = tgStatus === null; const tgConfigured = !!(tgStatus && tgStatus.configured); const tgLinked = !!(tgStatus && tgStatus.chatLinked); const tgUsername = (tgStatus && tgStatus.username) || 'SatmeliBot'; const tgDot = tgLoading ? 'var(--muted-3)' : (tgConfigured && tgLinked) ? 'var(--signal)' : (tgConfigured ? '#F5A623' : 'var(--muted-3)'); const tgText = tgLoading ? 'Verificando…' : !tgConfigured ? 'Sin configurar' : tgLinked ? 'Conectado · entregando alertas' : 'Falta vincular chat'; let tgAction = null; if (isAdmin && tgConfigured && !tgLinked) { tgAction = ( Abrir @{tgUsername} ); } else if (isAdmin && !tgConfigured) { tgAction = (
Falta cargar TELEGRAM_BOT_TOKEN en el servidor.
); } return (
CONEXIONES
} name="Mercado Libre" dot={meliDot} statusText={meliText} action={meliAction} />
} name="Telegram" dot={tgDot} statusText={tgText} action={tgAction} />
); } Object.assign(window, { App, UserAvatar, ConnectionsPanel });