Saltar a contenido

Dashboard

Panel web para administradores y clientes.

Acceso

https://mercadopago.histrix.com.ar/dashboard

Login

El dashboard acepta tanto el admin token como una client API key:

flowchart TD
    LOGIN["Pantalla de login"] --> ENTER["Ingresar API key"]
    ENTER --> VALIDATE["GET /oauth/validate"]
    VALIDATE --> TYPE{type?}
    TYPE -->|admin| ADMIN["Dashboard Admin<br/>Todas las tabs"]
    TYPE -->|client| CLIENT["Dashboard Cliente<br/>Tabs limitadas"]

Tabs disponibles

Tab Admin Client Descripción
Overview Estado OAuth, MP User ID, expiración del token
Sucursales Lista de stores con POS
Terminales Terminales con sucursal, caja y modo
Órdenes Buscar orden por ordenId
Webhooks Últimos webhook logs
API Keys Crear, listar y revocar keys
Clientes Info completa: OAuth + API key + tokens

Tecnología

No es React

El dashboard usa hono/jsx/dom, no React. Los imports son diferentes:

import { useState, useEffect } from 'hono/jsx';
import { render } from 'hono/jsx/dom';

Tecnología Uso
hono/jsx/dom Rendering
hono/client (hc) Llamadas tipadas a la API
InferResponseType Tipos de respuesta inferidos
Tailwind CSS v4 Estilos

Estructura de archivos

src/
├── client.tsx              # Mount point: render(<App />)
├── app.tsx                 # Layout + tabs + routing
├── components/
│   ├── StatusBadge.tsx     # Badge de estado con colores
│   ├── LoginScreen.tsx     # Pantalla de login
│   ├── Overview.tsx        # Estado OAuth
│   ├── Stores.tsx          # Sucursales + POS
│   ├── Terminals.tsx       # Terminales
│   ├── Orders.tsx          # Consultar orden
│   ├── ApiKeysPanel.tsx    # CRUD API keys (admin)
│   ├── ClientInfo.tsx      # Info del cliente (admin)
│   └── Webhooks.tsx        # Webhook logs
└── lib/
    └── api.ts              # createClient(token) con hc<AppType>

Patrón de componente

import type { InferResponseType } from 'hono/client';
import { useEffect, useState } from 'hono/jsx';
import { createClient } from '../lib/api';

export function MiComponente({ token }: { token: string }) {
  const client = createClient(token);
  // ⚠️ Segundo arg 200 para filtrar solo respuesta exitosa
  type Response = InferResponseType<typeof client.mi.ruta.$get, 200>;

  const [data, setData] = useState<Response | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const load = async () => {
      try {
        const res = await client.mi.ruta.$get();
        setData(await res.json());
      } catch (e) {
        console.error('[MiComponente] Failed:', e);
      }
      setLoading(false);
    };
    load();
  }, [token]);

  if (loading) return <div class="text-center py-10 text-slate-500">Cargando...</div>;
  // ... render con clases Tailwind
}