Skip to content

Componentes — Mush2 Frontend ​

Catálogo completo de componentes organizados por capas. Cada componente listado con su ubicación, props API, estados visuales y reglas de uso.


1. Layer Architecture ​

┌─────────────────────────────────────────────────────┐
│                    LAYOUT                            │
│  AppShell, Sidebar, TopBar, BottomNav, StatusFooter  │
│  Solo estructura. Sin estado propio. Sin API calls   │
├─────────────────────────────────────────────────────┤
│                     PAGES                            │
│  Home, Dashboard, DeviceDetail, Recipes, Cycles,     │
│  Settings, Provisioning, Landing                     │
│  Orquestan API + SSE + composites. Una por ruta.    │
├─────────────────────────────────────────────────────┤
│                   COMPOSITES                         │
│  MetricCard, StatusBadge, TerminalLog,               │
│  ActuatorControl, OfflineOverlay, ChartPanel,        │
│  SystemAlert, AuthModal, DevicesEmptyState,          │
│  RecipesEmptyState, DeviceHistoryChart               │
│  Combinan primitives + datos. Props: data callbacks. │
├─────────────────────────────────────────────────────┤
│                   PRIMITIVES                         │
│  Gauge, ArcGauge, DomeGauge, ToggleSwitch,           │
│  SegmentedBar, Skeleton, EmptyState, ErrorState,     │
│  LoadingState, OfflineBanner, ErrorBoundary          │
│  Sin lógica de negocio. Sin imports de API.         │
└─────────────────────────────────────────────────────┘

Layer rules (aplica en code review) ​

  • Primitive → no importa composites ni pages. No llama API.
  • Composite → puede importar primitives y llamar API (solo acciones de usuario).
  • Page → puede importar todo. Orchesta datos + estado. No contiene lógica de renderizado reutilizable.
  • Layout → no importa pages. Sin estado propio.

2. Layout components ​

AppShell ​

Ruta:    src/components/layout/AppShell.jsx
Export:  export default function AppShell({ user, onLogout, children })

Estructura que renderiza:

<div.app-shell>
  <Sidebar />
  <TopBar user={user} onLogout={onLogout} />
  <main.app-content>
    <OfflineBanner />
    {children}
  </main>
  <BottomNav />
  <StatusFooter />
</div>
Ruta:    src/components/layout/Sidebar.jsx
Export:  export default function Sidebar()
Props:   ninguna
Ítems:   Hub(/), Dashboard, Recipes, Cycles, Settings
  • El item activo usa .sidebar-item.active → bg-surface-variant + text-spore-green + FILL 1
  • Decorado con .sidebar-divider entre navegación principal y settings
  • Icon buttons inferiores: help, terminal

TopBar ​

Ruta:    src/components/layout/TopBar.jsx
Export:  export default function TopBar({ user, onLogout })
Props:   user: { username: string }, onLogout: fn
  • Brand: "MUSH2" (.topbar-brand)
  • Nav links: Dashboard(/), Recipes, Cycles, Settings
  • Right: notificaciones, settings icon, user avatar (primera letra), logout

BottomNav ​

Ruta:    src/components/layout/BottomNav.jsx
Export:  export default function BottomNav()
Props:   ninguna
Ítems:   Home(/), Dashboard, Recipes, Cycles, Settings
  • Visible solo en max-width: 480px (.bottom-nav { display: none } → display: flex)
  • env(safe-area-inset-bottom) para notch

StatusFooter ​

Ruta:    src/components/layout/StatusFooter.jsx
Export:  export default function StatusFooter()
Props:   ninguna
Contenido: "Mush2 OS v2.4.1 | SYS_UPTIME: --:--:--"

3. Primitives ​

Gauge ​

Ruta:    src/components/ui/Gauge.jsx
Export:  export default function Gauge({ variant, value, min = 0, max = 100, unit, label, size = 'md' })
PropTipoDefaultDescripción
variant'half' | 'donut''donut'Tipo de gauge
valuenumber—Valor actual
minnumber0Mínimo del rango
maxnumber100Máximo del rango
unitstring—Sufijo (ej: "%")
labelstring—Etiqueta (solo half)
size'sm' | 'md' | 'lg''md'Tamaño del gauge

Coloreado automático por zona vía getZoneColor(value, min, max):

  • < 20% o > 80% → var(--error-red)
  • < 30% o > 70% → var(--amber)
  • 30-70% → var(--spore-green)

ArcGauge ​

Ruta:    src/components/ui/ArcGauge.jsx
Export:  export default function ArcGauge({ value, min = 0, max = 100, unit = '%', color = 'primary', size = 'md', label, trend, errorState, errorMessage })
PropTipoDefaultDescripción
color'primary' | 'secondary' | 'tertiary' | 'error''primary'Color del arco
trendstring (SVG polyline points)—Sparkline de tendencia
errorStateboolean—Modo de error
errorMessagestring—Mensaje de error

DomeGauge ​

Ruta:    src/components/ui/DomeGauge.jsx
Export:  export default function DomeGauge({ value, prevValue, min, max, optMin, optMax, unit, label, decimals = 1, history = [], noData })
PropTipoDefaultDescripción
prevValuenumber—Valor anterior (delta)
optMinnumber—Límite inferior rango óptimo
optMaxnumber—Límite superior rango óptimo
historynumber[][]Array 12 valores historicos (sparkline)
noDataboolean—Muestra "--" sin datos

Contiene:

  • SVG semicírculo con needle animado (transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1))
  • Barra de gradiente óptimo (azul → verde → rojo)
  • Sparkline de 12 barras en base
  • Indicador de delta (↑ / ↓)
  • aria-label para accesibilidad

ToggleSwitch ​

Ruta:    src/components/ui/ToggleSwitch.jsx
Export:  export default function ToggleSwitch({ checked, onChange, disabled })

ARIA: role="switch", aria-checked, tabIndex, keyboard (Enter/Space). CSS classes: .toggle-switch.on / .toggle-switch.off, .toggle-knob.

SegmentedBar ​

Ruta:    src/components/ui/SegmentedBar.jsx
Export:  export default function SegmentedBar({ active = 0, total = 20, color = 'primary' })
PropTipoDefaultDescripción
activenumber0Segmentos activos
totalnumber20Total de segmentos
color'primary' | 'secondary' | 'amber' | 'error''primary'Color de segmentos activos

Skeleton ​

Ruta:    src/components/ui/Skeleton.jsx
Exports:
  SkeletonCard()
  SkeletonGrid({ count = 4 })
  SkeletonMetric()
  SkeletonTable({ rows = 3, cols = 4 })
ExportDescripción
SkeletonCard3 líneas (title, text, short) en .skeleton-card
SkeletonGridGrid de count skeleton cards
SkeletonMetric2 líneas (value + label) en .skeleton-metric
SkeletonTableGrid rows x cols de celdas

EmptyState ​

Ruta:    src/components/ui/EmptyState.jsx
Export:  export default function EmptyState({ icon = 'inbox', title, message, action })
PropTipoDescripción
action{ label: string, onClick: fn }Botón de acción opcional

ErrorState ​

Ruta:    src/components/ui/ErrorState.jsx
Export:  export default function ErrorState({ message, onRetry })
  • Default message: 'CONNECTION_LOST'
  • Default button: 'RETRY_UPLINK'

LoadingState ​

Ruta:    src/components/ui/LoadingState.jsx
Export:  export default function LoadingState({ message = 'Loading...', icon = 'sync' })

Centrado vertical min-h-[60vh], ícono animado + mensaje.

OfflineBanner ​

Ruta:    src/components/ui/OfflineBanner.jsx
Export:  export default function OfflineBanner()

Sin props. Usa navigator.onLine internamente vía useEffect. Banner rojo "CONNECTION LOST".

ErrorBoundary ​

Ruta:    src/components/ui/ErrorBoundary.jsx
Export:  class ErrorBoundary extends Component

Class component. Fallback: "FATAL_EXCEPTION" + "REBOOT_SYSTEM" button.


4. Composites ​

MetricCard ​

Ruta:    src/components/ui/MetricCard.jsx
Export:  export default function MetricCard({ icon, label, value, unit, trend, className, children })
PropTipoDescripción
iconstringNombre Material Symbol
trendnumberPositivo = green ↑, negativo = red ↓
childrenReactNodeContenido extra bajo value row

Base CSS: .glass-card.p-4.rounded-xl.flex.flex-col.gap-3

⚠️ También existe src/components/dashboard/MetricCard.jsx (legacy). Usar la versión de ui/ para componentes nuevos.

StatusBadge ​

Ruta:    src/components/ui/StatusBadge.jsx
Export:  export default function StatusBadge({ status = 'online', label, pulse = true })
PropTipoDefaultDescripción
status'online' | 'offline' | 'critical''online'Estado
pulsebooleantrueAnimación de pulso en dot

CSS: .status-badge.{status} → contiene .status-dot.{status} con opcional .pulse.

TerminalLog ​

Ruta:    src/components/ui/TerminalLog.jsx
Export:  export default function TerminalLog({ entries = [], onExtract })
PropTipoDescripción
entriesArray<{ ts: string, text: string, type?: 'info' | 'ok' | 'warn' | 'err' }>Entradas del log
onExtractfnBotón "FULL_EXTRACT.SH"

Tipos de entrada: .msg-ok (spore-green), .msg-warn (amber), .msg-err (error-red), .msg-info (on-surface-variant).

ActuatorControl ​

Ruta:    src/components/device/ActuatorControl.jsx
Export:  export default function ActuatorControl({ actuator, meta, cmdState, onToggle, disabled })
PropTipoDescripción
actuator{ channel, state, mode }Datos del actuador
meta{ label, icon, sublabel }Metadatos (usa DEFAULT_META si no se provee)
cmdState'PENDING' | 'TIMEOUT' | stringEstado del comando
onToggle(channel) => voidCallback toggle

Estados visuales: ON (verde), OFF (standby), ERROR/TIMEOUT (rojo), PENDING (amber). Contiene Toggle local con inline styles.

SystemAlert ​

Ruta:    src/components/ui/SystemAlert.jsx
Export:  export default function SystemAlert({ message, onReconnect, onViewLogs })

Modal crítico a pantalla completa con:

  • Línea de escaneo animada (.scan)
  • Countdown circular SVG con auto-retry cada 8s
  • Botones: "FORCE RECONNECT", "VIEW SYSTEM LOGS"
  • Efecto glitch en línea decorativa
  • Esquinas decorativas

ChartPanel ​

Ruta:    src/components/ui/ChartPanel.jsx
Export:  export default function ChartPanel({ deviceId, telemetry, has })
PropTipoDescripción
deviceIdstringID del dispositivo para fetch
telemetry{ temperature, humidity, co2, voc }Datos actuales
has{ temp, hum, eco2, tvoc }Flags de sensores disponibles
  • Dos paneles Chart.js: Temp+Hum (izquierda), eCO₂+TVOC (derecha)
  • Bands de rango óptimo dibujadas como plugin Chart.js
  • Time ranges: 15m, 1H, 6H, 1D, 3D, 7D
  • Toggle de visibilidad por sensor
  • Fetch interno: getTelemetryHistory(deviceId, { limit, resolution })

DeviceHistoryChart ​

Ruta:    src/components/ui/DeviceHistoryChart.jsx
Export:  export default function DeviceHistoryChart({ title, datasets, bands, labels, margin = 0.10 })

Chart.js wrapper reutilizable. Sin fetch interno — recibe datos por props. Mismos patrones de tooltip, grid, font que ChartPanel.

DevicesEmptyState ​

Ruta:    src/components/ui/DevicesEmptyState.jsx
Export:  export default function DevicesEmptyState({ onConnect })
  • Ícono grande sensors_off con glow
  • Botones: "ADD DEVICE" (navega a /provisioning), "RETRY"
  • Navegación interna vía useNavigate

RecipesEmptyState ​

Ruta:    src/components/ui/RecipesEmptyState.jsx
Export:  export default function RecipesEmptyState({ onCreate })
  • Ícono grande potted_plant con glow
  • Botón: "CREATE RECIPE"

OfflineOverlay ​

Ruta:    src/components/ui/OfflineOverlay.jsx
Export:  export default function OfflineOverlay({ lastSeen = '--:--:--', onRetry })

Overlay absoluto con backdrop blur. Muestra "CONNECTION INTERRUPTED", timestamp "LAST SEEN", botón "RETRY CONNECTION" con spinner.

AuthModal ​

Ruta:    src/components/auth/AuthModal.jsx
Export:  ???

Modal de autenticación. (Ver Landing.jsx y useAuth.js para flujo completo.)


5. Pages ​

Cada página sigue la estructura de 4 estados:

jsx
if (loading) return <LoadingState />
if (error && data.length === 0) return <ErrorState message={error} onRetry={refetch} />
if (data.length === 0) return <EmptyState />

return <div>{/* contenido real */}</div>
RutaComponenteArchivo
* (no auth)Landingpages/Landing.jsx
/Homepages/Home.jsx
/dashboardDashboardpages/Dashboard.jsx
/devices/:idDeviceDetailpages/DeviceDetail.jsx
/recipesRecipespages/Recipes.jsx
/cyclesCyclespages/Cycles.jsx
/settingsSettingspages/Settings.jsx
/provisioningProvisioningpages/Provisioning.jsx

6. Sistema de botones ​

ClaseEstiloUso
.btn.btn-primaryFondo spore-green, texto on-primaryAcción principal
.btn.btn-secondaryBorde outline-variant, transparenteAcción secundaria
.btn.btn-dangerFondo error/20, borde error/40Destrucción/riesgo
.btn.btn-ghostTransparente, hover spore-greenAcción sutil
.btn.btn-surfaceFondo surface-container-highAcción neutral

Todas las variantes comparten: .btn → font-mono, 10px, weight 700, letter-spacing 0.1em, uppercase.


7. Convenciones de código ​

ReglaEstándar
ArchivosPascalCase para componentes (ArcGauge.jsx), camelCase para hooks/utilidades (useSSE.js)
Exportsexport default function ComponentName() — sin default anónimo
PropsDesestructurar en la firma. Tipado opcional (JS puro).
Event handlershandleToggle, handleSubmit, handleRetry. Pasar referencias, no arrow functions inline.
Loading/erroruseState local en la page, nunca elevado al layout.
React.memoEn primitives con props que cambian frecuentemente (Gauge, SegmentedBar).
List keysUsar id del backend, nunca índice del array.
SSE cleanupreturn () => { cancelled = true } en useEffect.
FetchSolo en pages y composites (ChartPanel). Primitives reciben datos por props.

Mush2 — Sistema IoT de control ambiental