EnlacesPanel v2 — Documentacion del componente
Componente reutilizable de gestion de enlaces swObj-hwObj
Refactorizado en s12 (2026-04-04) — modular, autocontenido, documentado
1. Vision y proposito
El EnlacesPanel gestiona los enlaces entre instrumentos de software (swObjs) y dispositivos de hardware (hwObjs). Permite crear, editar y eliminar enlaces N:M con exclusividad atomica por canal, sincronizacion en tiempo real con InstrumentBridge_03, y persistencia de configuraciones via escenarios.
Principio de diseno: El componente lee datos del RegistryContext y EscenariosContext directamente — no necesita props de datos. Los hwObjs pueden ser fisicos (ble_07BCF8) o virtuales (sim1_eng) — al componente le da igual.
Estado actual (s12): Componente 100% autocontenido en src/components/EnlacesPanel/. Refactorizado de un monolito de 587 lineas a 8 modulos con responsabilidad unica. Funciona en Col4 (panel flotante) y Sim1 (slot PanelGrid) simultaneamente.
2. Arquitectura post-refactor
src/components/EnlacesPanel/
├── index.ts barrel: exports Content + Float + types
├── types.ts EnlacesPanelContentProps + re-exports tipos
├── helpers.ts buildDefaultChannelMap, findExistingLink, getBridgeForSwObj
├── useBridgeSync.ts hook: enlazar/desenlazar con sync Bridge DOM
├── EnlacesPanelContent.tsx orquestador (~195 lin) — compose secciones
├── MatrixCell.tsx celda interactiva con editor channelMap
├── EscenariosSection.tsx CRUD escenarios con estado propio
├── ObjectsSection.tsx lista colapsable swObjs/hwObjs
├── epc.css root .epc_root + header
├── EnlacesPanel.css estilos matriz + celdas + editor + objetos
├── EnlacesPanel_escenarios.css estilos seccion escenarios
│
├── hw/ sub-componentes de hardware fisico
│ ├── useEspSensorControl.ts hook MQTT timer/Auto-Delta ESP32
│ ├── EspTimerSelect.tsx selector intervalo + boton config
│ ├── EspTimerSelect.css
│ ├── AutoDeltaPanel.tsx panel histeresis Auto-Delta
│ ├── AutoDeltaPanel.css
│ ├── HwObjPopup.tsx popup 3 tabs (estado/control/sensores)
│ └── HwObjPopup.css
│
└── float/ wrapper flotante Col4
├── index.tsx position:fixed, drag, resize, minimizar
└── EnlacesPanelFloat.css
2.1 Separacion de responsabilidades
| Capa | Responsabilidad | Fichero(s) |
| Tipos | Props del componente y re-exports de tipos del dominio (ObjId, ChannelMap, etc.) | types.ts |
| Helpers | Funciones puras sin dependencia React: channel maps, bridge access | helpers.ts |
| Hooks | Sincronizacion RegistryContext con InstrumentBridge_03 DOM | useBridgeSync.ts |
| Orquestador | Contextos, compose matriz + secciones + popup. Sin logica de negocio propia | EnlacesPanelContent.tsx |
| Matriz | Celda interactiva: enlazar/desenlazar, editor channelMap, exclusividad atomica | MatrixCell.tsx |
| Escenarios | CRUD completo: guardar, cargar, importar/exportar JSON, edicion inline, escenario inicial | EscenariosSection.tsx |
| Objetos | Lista presentacional de hwObjs y swObjs registrados | ObjectsSection.tsx |
| HW | Control de hardware fisico: timer ESP, Auto-Delta, popup 3 tabs. Solo para hwObjs fisicos | hw/*.tsx |
| Float | Contenedor flotante Col4: position:fixed, drag, resize, minimizar | float/index.tsx |
2.2 Flujo de datos
RegistryContext ──→ EnlacesPanelContent (orquestador)
├── MatrixCell × N (lee hwObjs/swObjs/enlaces)
│ └── onEnlazar/onDesenlazar → useBridgeSync
│ ├── RegistryContext.actions.enlazar()
│ └── InstrumentBridge_03.enlazar() (DOM sync)
├── EscenariosSection (lee EscenariosContext directo)
│ └── guardar/cargar/importar/exportar
└── ObjectsSection (presentacional, solo props)
EscenariosContext ──→ EscenariosSection (acceso directo via useEscenarios)
HwObjsContext ────→ EnlacesPanelContent → handleModoChange → HwObjPopup
3. Inventario de ficheros
| Fichero | Lineas | Funcion |
index.ts | 5 | Barrel: EnlacesPanelContent, EnlacesPanelContentProps, EnlacesPanel (float) |
types.ts | 23 | Props + re-exports de ObjId, HwObjsMap, SwObjsMap, MatrizEnlaces, ChannelMap |
helpers.ts | 70 | getBridgeForSwObj, buildDefaultChannelMap, findExistingLink |
useBridgeSync.ts | 65 | Hook: handleEnlazar, handleDesenlazar, handleUpdateChannelMap |
EnlacesPanelContent.tsx | 195 | Orquestador: contextos, matriz, secciones, HwObjPopup, modoChange |
MatrixCell.tsx | 175 | Celda: click enlazar, editor channelMap, exclusividad atomica, AutoDeltaPanel |
EscenariosSection.tsx | 175 | CRUD escenarios: formulario guardar, lista, edicion inline, import/export |
ObjectsSection.tsx | 65 | Lista colapsable hwObjs + swObjs con badges de tipo y contadores |
epc.css | 46 | Root .epc_root + header .epc_header |
EnlacesPanel.css | 617 | Matriz, celdas, editor, botones, objetos, resize |
EnlacesPanel_escenarios.css | ~230 | Seccion escenarios: save, items, badges, acciones |
3.1 Sub-componentes HW
| Fichero | Lineas | Funcion |
hw/useEspSensorControl.ts | ~260 | Hook MQTT: timer, Auto-Delta, sensor histeresis, status/capabilities |
hw/EspTimerSelect.tsx | ~95 | Select intervalo + dot estado + boton config Auto-Delta |
hw/AutoDeltaPanel.tsx | ~240 | Panel config histeresis: modo pct/abs, heartbeat, apply |
hw/HwObjPopup.tsx | ~500 | Popup 3 tabs: Estado, Control (router+sim+timer), Sensores (tabla) |
3.2 Wrapper flotante
| Fichero | Lineas | Funcion |
float/index.tsx | 180 | Contenedor position:fixed. Drag por titlebar, resize por grip, minimizar. Monta <EnlacesPanelContent hideHeader /> |
float/EnlacesPanelFloat.css | 123 | Estilos: .ep_panel, .ep_titlebar, .ep_resize_grip |
4. EnlacesPanelContent (orquestador)
Fichero principal (~195 lineas). No contiene logica de negocio propia — delega a sub-componentes y hooks. Su responsabilidad es:
- Leer contextos (RegistryContext, HwObjsContext)
- Calcular IDs memorizados (hwObjIds, swObjIds, enlaceCount)
- Instanciar
useBridgeSync para obtener handlers sincronizados
- Renderizar la tabla matriz con
MatrixCell por celda
- Montar
EscenariosSection y ObjectsSection como secciones colapsables
- Gestionar
HwObjPopup y handleModoChange
4.1 Props
| Prop | Tipo | Default | Uso |
className | string? | — | Clase CSS adicional en el root |
style | CSSProperties? | — | Estilos inline en el root |
hideHeader | boolean? | false | Oculta el header compacto (cuando el contenedor ya tiene titlebar) |
5. MatrixCell
Celda interactiva de la matriz swObj x hwObj. Cada celda muestra el estado del enlace y permite editarlo.
5.1 Estados visuales
| Estado | Clase CSS | Visual |
| Sin enlace | .ep_cell.empty | + |
| Enlazado activo | .ep_cell.linked | 🔗 2/3 |
| Enlazado inactivo | .ep_cell.linked.inactive | ⏸ 0/3 |
| Expandido (editor) | .ep_cell.linked.expanded | Editor channelMap visible |
5.2 Editor de channelMap
Al hacer click en una celda enlazada, se expande el editor que muestra cada mapping canal → puerto con selects. Incluye:
- Exclusividad atomica: al aplicar, desactiva canales conflictivos en otros hwObjs del mismo swObj
- Indicador de conflicto: icono ⚠ si un canal ya esta activo en otro hwObj
- Boton Auto-Delta: visible solo para hwObjs fisicos, abre
AutoDeltaPanel en modo sensor
6. EscenariosSection
Seccion colapsable que gestiona escenarios de enlaces. Accede directamente a useEscenarios() — estado propio de UI, sin props de datos.
6.1 Operaciones
| Operacion | UI | Accion |
| Guardar | Input nombre + descripcion + boton | actions.guardarEscenario(nombre, desc) |
| Cargar | Boton por item | actions.cargarEscenario(id) |
| Actualizar | 🔄 (confirm) | actions.actualizarEscenario(id) |
| Renombrar | Double-click → edit inline | actions.renombrarEscenario(id, nombre, desc) |
| Escenario inicial | ☆/⭐ toggle | actions.setEscenarioInicial(id|null) |
| Exportar | 📤 boton | actions.exportarEscenario(id) → descarga JSON |
| Importar | 📥 + file input | actions.importarEscenario(json) |
| Eliminar | 🗑️ (confirm) | actions.eliminarEscenario(id) |
| Modo libre | 🔓 boton | actions.desactivarEscenario() |
6.2 Escenarios vs Scenarios
Escenarios (EscenariosContext) = que esta conectado a que (enlaces swObj-hwObj).
Scenarios (Sim1/sim-catalog) = como se comportan los sensores (fisica de vuelo).
Son complementarios, no sustitutos. Un escenario de enlaces funciona con cualquier scenario de simulacion.
7. ObjectsSection
Componente presentacional. Recibe datos por props (hwObjs, swObjs, enlaces, ids). Muestra dos listas colapsables con badges de tipo y contadores.
Cada item muestra: badge tipo (physical/virtual/animate/component), nombre, y metadata (puertos, subs, enlaces, emitsAs).
8. useBridgeSync
Hook que envuelve las acciones del RegistryContext con sincronizacion DOM. Cada operacion hace dos cosas:
- 1. Actualiza el estado React via
actions.enlazar() / actions.desenlazar()
- 2. Sincroniza con
InstrumentBridge_03.registry via bridge.enlazar() / bridge.desenlazar()
8.1 API
| Handler | Params | Logica extra |
handleEnlazar | sw, hw, map | Desenlaza hwObjs huerfanos del bridge antes de enlazar el nuevo |
handleDesenlazar | sw, hw | Directa |
handleUpdateChannelMap | sw, hw, map | Re-enlaza con el nuevo map (sin cleanup) |
9. Helpers
Funciones puras sin dependencia de React. Fichero helpers.ts.
| Funcion | Uso |
getBridgeForSwObj(swObj) | Acceso tipado a window.InstrumentBridge_03.registry. Retorna la instancia o null. Centraliza el cast (window as any) |
buildDefaultChannelMap(sw, hw, ...) | Genera channelMap por defecto al enlazar. Respeta exclusividad: si el swObj ya tiene enlaces, todos los canales van inactivos |
findExistingLink(sw, canal, hw, enlaces) | Busca si un canal ya tiene enlace activo en otro hwObj. Devuelve el hwObjId del conflicto o null |
10. Sub-componentes HW
Subdirectorio hw/ — controles especificos de hardware fisico (ESP32). Se ocultan automaticamente para hwObjs virtuales.
10.1 useEspSensorControl
Hook central de comunicacion MQTT con el ESP. Gestiona timer periodico, modo Auto-Delta, histeresis por sensor, y capabilities. Importado por EspTimerSelect y AutoDeltaPanel.
10.2 EspTimerSelect
Selector compacto: dot de estado (coloreado segun modo) + select de intervalo + boton ⚙ que abre AutoDeltaPanel en modo global.
10.3 AutoDeltaPanel
Panel de configuracion de histeresis Auto-Delta. Dos modos: global (desde EspTimerSelect) y sensor (desde MatrixCell editor). Toggle pct/abs, selector umbral, heartbeat.
10.4 HwObjPopup
Panel flotante draggable con 3 tabs. Position:fixed, z-index 1300.
| Tab | Contenido |
| 📊 Estado | Tipo, router, conexion, Rx/Tx, simInhibited, uptime, sensores |
| ⚙️ Control | Grid botones router, toggle sim/HwReal, config publicacion (timer/autoΔ/stop) |
| 🔬 Sensores | Tabla puertos: tipo (chip), valor, rango, barra 3px |
Para hwObjs virtuales: El popup funciona pero los controles de hardware (reboot, sim toggle, timer config) no aplican. Pendiente: filtrar por hwObj.type === 'virtual' y mostrar info de sistema.
11. Wrapper flotante (Col4)
Subdirectorio float/ — contenedor position:fixed para el panel de enlaces en la vista Col4 (Detalle01).
- Drag: por titlebar, mousedown/mousemove/mouseup
- Resize: grip esquina inferior derecha
- Minimizar: boton ▲/▼ en titlebar
- Responsive: recalcula layout al cambiar dispositivo o resize viewport via ViewportLayoutContext
Monta <EnlacesPanelContent hideHeader /> con su propio titlebar.
Importado por Detalle01.tsx via el barrel: import { EnlacesPanel } from '../../components/EnlacesPanel'
12. Estructura CSS
12.1 Prefijos de clases
| Prefijo | Fichero | Ambito |
.epc_* | epc.css | Root y header del componente |
.ep_* | EnlacesPanel.css | Matriz, celdas, editor, botones, objetos |
.ep_esc_* | EnlacesPanel_escenarios.css | Seccion escenarios |
.ep_panel, .ep_titlebar* | float/EnlacesPanelFloat.css | Solo contenedor flotante Col4 |
.esp_timer_* | hw/EspTimerSelect.css | Selector timer ESP |
.adp_* | hw/AutoDeltaPanel.css | Panel Auto-Delta |
.hwp_* | hw/HwObjPopup.css | Popup hwObj |
12.2 Temas
El componente usa colores hardcoded oscuros (#1a1a2e, #16213e, #3a3a5c) heredados del diseno original. No usa variables --sim1-*. Funciona con temas oscuros pero puede contrastar con el tema Daylight.
13. Contextos React consumidos
| Contexto | Consumido por | Que lee |
RegistryContext | EnlacesPanelContent | hwObjs, swObjs, enlaces, actions |
HwObjsContext | EnlacesPanelContent | setHwObjs para handleModoChange |
EscenariosContext | EscenariosSection | escenarios, escenarioActivoId, escenarioInicialId, actions |
Regla: El componente NUNCA importa ViewportLayoutContext — eso es responsabilidad del contenedor flotante (float/index.tsx).
14. Como montar el componente
14.1 Ejemplo minimo
import { EnlacesPanelContent } from '@/components/EnlacesPanel';
function MyPanel() {
return (
<div style={{ width: 400, height: 600 }}>
<EnlacesPanelContent />
</div>
);
}
Requisitos: RegistryProvider, EscenariosProvider y HwObjsContext.Provider mas arriba en el arbol React (ya estan en App.tsx).
14.2 Con header oculto (contenedor con titlebar propio)
<EnlacesPanelContent hideHeader style={{ flex: '1 1 auto', minHeight: 0 }} />
14.3 Como panel del PanelGrid
// En registry.ts:
const EnlacesPanelW = lazy(() => import('./EnlacesPanelWrapper'));
{ id: 'sim-enlaces', label: 'Enlaces', icon: '⚡',
component: EnlacesPanelW, minSize: { cols: 1, rows: 2 },
accepts: ['bridges', 'enlaces', 'sensors'] }
14.4 Como panel flotante en Col4
import { EnlacesPanel } from '../../components/EnlacesPanel';
<EnlacesPanel visible={showEnlacesPanel} onClose={() => setShowEnlacesPanel(false)} />
LRU Platform — EnlacesPanel v2 — 2026-04-04 — Refactorizado en s12: 8 modulos, 3 subdirectorios, ~800 lineas componente + ~850 lineas hw