• Biblioteca EQUILIBRIUM equilibrium-system.com

EQUILIBRIUM: kit de interfaz de usuario en Figma

EQUILIBRIUM UI KIT / FIGMA Documento. Vamos a hacer esto como un verdadero documento de la UI bajo Figma, no solo una descripción. Puedes devolvérselo a un diseñador o puedes recogerlo tú mismo. Dimos:

EQUILIBRIUM UI KIT / FIGMA

ESTRUCTURA FAIL FIGMA

Crea un archivo: EQUILIBRIUM / INTERFACE V1

Páginas (Pages):

PAGE 00 — FOUNDATIONS

Sistema de colores

Principales:

Antecedentes:

Superficie:

Impresión

Efectos

PAGE 01 — COMPONENTS

Node (ruido). Tamaño:

  • S: 16px
  • M: 32px
  • L: 64px

Propiedades:

  • color = tipo
  • Tamaño = efecto

Antecedentes:

  • normal
  • hovered
  • active
  • overloaded (volumen)

HyperEdge (hiperrebro). No es una línea.

Visual:

  • Nube suave
  • gradiente
  • blur

Opciones

  • Débil
  • Activado
  • sobrecargado
  • conflictivo

Flow Line

  • Línea fina
  • animación del movimiento
  • dirección

Panel (mapa)

  • Usada a la derecha.

Tamaño

  • Ancho: 360–420px

Contiene:

  • Título
  • Unidades de datos
  • botones

Button

Tipos:

  • Primary
  • Secondary
  • Ghost

Mode Switch. Interruptor:

  • Sinopsis
  • Diagnostico
  • Escenario
  • Acción

Timeline

  • Escala horizontal
  • draggable
  • marcas de eventos

PAGE 02 — SCREENS

SCREEN 1 — GLOBAL DASHBOARD

Frame 1440 × 900 (desktop)

Layout

TOP BAR (64px)


LEFT PANEL (240px) | CENTER GRAPH | RIGHT PANEL (400px)


BOTTOM TIMELINE (120px)

TOP BAR

De izquierda a derecha:

  • Logo (EQUILIBRIUM)
  • Search
  • Level selector
  • Mode switch
  • AI button
  • Settings

LEFT PANEL

Sections:

  • Tipos de nodos
  • Tipos de vínculos
  • Deliberaciones

- Usa los cheques.

CENTER — GRAPH AREA

Es el marco principal.

Dentro:

  • Nodes (Auto Layout disabled)
  • HyperEdges (under layer)
  • Flows (top layer animation) 👉 Utiliza posicionamiento absoluto

RIGHT PANEL

Estado 1 Node Vie

  • Title
  • Type
  • Metrics
  • Connections
  • Buttons

Estado financiero 2 Edge View

  • Significado
  • Asistencia
  • Recursos
  • Efecto
  • Metrics

BOTTOM — TIMELINE

  • línea horaria
  • punto de acontecimiento
  • drag

PAGE 03 — FLOWS

Flow 1 ○ Selección de un nodo

Click

- Resaltar

- Abrir el panel

Flow 2 ○ Selección de hiperrrebras

Click

- La divulgación del protocolo;

Flow 3

  • seleccionar un nodo
  • clic en
  • seleccionar tipo
  • Rellenar campos

Flow 4 - Diagnóstico

Cambia de modo

- Resaltar los problemas

Flow 5 ○ Escenario

Cambia la opción de configuración

○ Actualización de la columna

COMPONENTE PARA EL START (mínimo)

Si quieres hacerlo rápido:

Hazlo:

  • Node (1 componente)
  • Panel
  • Graph area
  • Top bar

- Es suficiente para el primer prototipo.

REGLAMENTO CRÍTICO (Recordar)

  • ○ No sobrecargas la pantalla
  • - No hagas pequeños elementos.
  • No hagas formas complicadas.

3 Principios básicos:

- Simplicidad

Leer

- Sentido de un sistema vivo




Acero MVP

MVP

Vamos al código para que tengamos un esqueleto MVP, no un conjunto de palabras bonitas. A continuación:

  • Acero,
  • la estructura del proyecto,
  • modelos de datos,
  • Frente en React,
  • Stor,
  • API,
  • Neo4j-scemum,
  • Primer escenario de trabajo.

Ahora es razonable tomar un React Flow para el gráfico visual; en la versión actual se utiliza un paquete @xyflow/react, y los nodos de casta se fabrican con los componentes React normales. Para el BD del gráfico, es natural que el neo4j-driver sea oficial. 

Acero MVP

Frontend

  • Next.js / React
  • TypeScript
  • @xyflow/react
  • Zustand

Backend

  • Next.js Route Handlers o Node/Express
  • TypeScript
  • Neo4j
  • neo4j-driver

¿Por qué?

  • React Flow ya está encerrado bajo node-based UI.  
  • Neo4j está bien, porque necesitamos nudos, conexiones, tipos de relación y preguntas. El controlador JS oficial se mantiene directamente.  


Estructura del proyecto

equilibrium-mvp/

├─ app/

│  ├─ page.tsx

│  ├─ api/

│  │  ├─ graph/route.ts

│  │  ├─ node/route.ts

│  │  └─ edge/route.ts

├─ components/

│  ├─ graph/

│  │  ├─ EquilibriumCanvas.tsx

│  │  ├─ nodes/

│  │  │  ├─ MeaningNode.tsx

│  │  │  ├─ ActorNode.tsx

│  │  │  └─ EffectNode.tsx

│  │  ├─ panels/

│  │  │  ├─ LeftFilters.tsx

│  │  │  ├─ RightInspector.tsx

│  │  │  └─ BottomTimeline.tsx

│  │  └─ TopBar.tsx

├─ lib/

│  ├─ neo4j.ts

│  ├─ mapper.ts

│  └─ constants.ts

├─ store/

│  └─ equilibrium.store.ts

├─ types/

│  └─ equilibrium.ts

├─ cypher/

│  ├─ schema.cypher

│  ├─ seed.cypher

│  └─ queries.cypher

├─ package.json

└─ .env.local


Tipos de datos

Empieza con 12 tipos, no con 6. Si no, te ahogas.

types/equilibrium.ts

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect';


export type EQEdgeType =

  | 'intent'

  | 'cooperation'

  | 'production'

  | 'feedback'

  | 'evolution';


export type EQState =

  | 'harmony'

  | 'tension'

  | 'crisis'

  | 'growth'

  | 'transformation'

  | 'unknown';


export interface EQMetrics {

  trust?: number;

  activity?: number;

  load?: number;

  influence?: number;

  stability?: number;

  ethics?: number;

}


export interface EQNodeData {

  label: string;

  nodeType: EQNodeType;

  description?: string;

  state: EQState;

  metrics: EQMetrics;

  tags?: string[];

}


export interface EQNode {

  id: string;

  type: EQNodeType;

  position: { x: number; y: number };

  data: EQNodeData;

}


export interface EQEdgeData {

  label: string;

  edgeType: EQEdgeType;

  meaning?: string;

  process?: string;

  norm?: string;

  metric?: string;

  ethics?: string;

  intensity?: number;

  risk?: number;

  state: EQState;

}


export interface EQEdge {

  id: string;

  source: string;

  target: string;

  type: 'default' | 'hyper';

  data: EQEdgeData;

}


export interface GraphPayload {

  nodes: EQNode[];

  edges: EQEdge[];

}

Zustand store

store/equilibrium.store.ts

'use client';


import { create } from 'zustand';

import type { GraphPayload, EQNode, EQEdge, EQState, EQNodeType, EQEdgeType } from '@/types/equilibrium';


interface FiltersState {

  nodeTypes: EQNodeType[];

  edgeTypes: EQEdgeType[];

  states: EQState[];

}


interface EquilibriumStore {

  graph: GraphPayload;

  selectedNode: EQNode | null;

  selectedEdge: EQEdge | null;

  filters: FiltersState;


  setGraph: (graph: GraphPayload) => void;

  selectNode: (node: EQNode | null) => void;

  selectEdge: (edge: EQEdge | null) => void;

  updateFilters: (filters: Partial<FiltersState>) => void;

  upsertNode: (node: EQNode) => void;

  upsertEdge: (edge: EQEdge) => void;

}


export const useEquilibriumStore = create<EquilibriumStore>((set) => ({

  graph: { nodes: [], edges: [] },

  selectedNode: null,

  selectedEdge: null,

  filters: {

    nodeTypes: ['meaning', 'actor', 'process', 'resource', 'norm', 'effect'],

    edgeTypes: ['intent', 'cooperation', 'production', 'feedback', 'evolution'],

    states: ['harmony', 'tension', 'crisis', 'growth', 'transformation', 'unknown'],

  },


  setGraph: (graph) => set({ graph }),

  selectNode: (node) => set({ selectedNode: node, selectedEdge: null }),

  selectEdge: (edge) => set({ selectedEdge: edge, selectedNode: null }),

  updateFilters: (filters) =>

    set((state) => ({

      filters: { ...state.filters, ...filters },

    })),


  upsertNode: (node) =>

    set((state) => {

      const exists = state.graph.nodes.some((n) => n.id === node.id);

      return {

        graph: {

          ...state.graph,

          nodes: exists

            ? state.graph.nodes.map((n) => (n.id === node.id ? node : n))

            : [...state.graph.nodes, node],

        },

      };

    }),


  upsertEdge: (edge) =>

    set((state) => {

      const exists = state.graph.edges.some((e) => e.id === edge.id);

      return {

        graph: {

          ...state.graph,

          edges: exists

            ? state.graph.edges.map((e) => (e.id === edge.id ? edge : e))

            : [...state.graph.edges, edge],

        },

      };

    }),

}));


Componente de columna

React Flow Renderitis nodes, edges y procesa la interacción a través de colbaquis como onNodesChange. Eso es exactamente lo que necesitamos para el lienzo básico. 

components/graph/EquilibriumCanvas.tsx


'use client';


import { useEffect, useMemo } from 'react';

import {

  Background,

  Controls,

  MiniMap,

  ReactFlow,

  ReactFlowProvider,

  addEdge,

  useEdgesState,

  useNodesState,

  Connection,

  Edge,

  NodeMouseHandler,

  EdgeMouseHandler,

} from '@xyflow/react';

import '@xyflow/react/dist/style.css';


import { useEquilibriumStore } from '@/store/equilibrium.store';

import MeaningNode from './nodes/MeaningNode';

import ActorNode from './nodes/ActorNode';

import EffectNode from './nodes/EffectNode';


const nodeTypes = {

  meaning: MeaningNode,

  actor: ActorNode,

  effect: EffectNode,

};


function CanvasInner() {

  const { graph, selectNode, selectEdge } = useEquilibriumStore();


  const initialNodes = useMemo(() => graph.nodes, [graph.nodes]);

  const initialEdges = useMemo(

    () =>

      graph.edges.map((e) => ({

        ...e,

        animated: e.data.state === 'growth',

        style: {

          strokeWidth: 2,

        },

      })),

    [graph.edges]

  );


  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);


  useEffect(() => setNodes(initialNodes), [initialNodes, setNodes]);

  useEffect(() => setEdges(initialEdges), [initialEdges, setEdges]);


  const onConnect = (connection: Connection) => {

    setEdges((eds) =>

      addEdge(

        {

          ...connection,

          type: 'default',

          data: {

            label: 'New relation',

            edgeType: 'cooperation',

            state: 'unknown',

          },

        },

        eds

      )

    );

  };


  const onNodeClick: NodeMouseHandler = (_, node) => {

    selectNode(node as any);

  };


  const onEdgeClick: EdgeMouseHandler = (_, edge) => {

    selectEdge(edge as any);

  };


  return (

    <div style={{ width: '100%', height: '100%' }}>

      <ReactFlow

        nodes={nodes}

        edges={edges as Edge[]}

        onNodesChange={onNodesChange}

        onEdgesChange={onEdgesChange}

        onConnect={onConnect}

        onNodeClick={onNodeClick}

        onEdgeClick={onEdgeClick}

        nodeTypes={nodeTypes}

        fitView

      >

        <MiniMap />

        <Controls />

        <Background gap={20} />

      </ReactFlow>

    </div>

  );

}


export default function EquilibriumCanvas() {

  return (

    <ReactFlowProvider>

      <CanvasInner />

    </ReactFlowProvider>

  );

}

Nodos de atómico

En React Flow, los nudos de casta son los componentes de react que se transmiten en nodeTypes. Es un camino que se apoya oficialmente. 

components/graph/nodes/MeaningNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function MeaningNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(123, 97, 255, 0.22)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{{fontSize: 12, opacity: 0.7 }}}El propósito </div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Ética: {data.metrics?.ethics?? '-'}

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

components/graph/nodes/ActorNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function ActorNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(77, 163, 255, 0.18)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{{fontSize: 12, opacity: 0.7 }}>Sujeto </div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Confianza: {data.metrics?.trust ?? '-'}

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

components/graph/nodes/EffectNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function EffectNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(61, 220, 151, 0.18)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{{fontSize: 12, opacity: 0.7}}>Effect </div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Estabilidad: {data.metrics?...stability ?? '-'}

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

Página principal

app/page.tsx

'use client';


import { useEffect } from 'react';

import EquilibriumCanvas from '@/components/graph/EquilibriumCanvas';

import { useEquilibriumStore } from '@/store/equilibrium.store';


export default function HomePage() {

  const { setGraph, selectedNode, selectedEdge } = useEquilibriumStore();


  useEffect(() => {

    fetch('/api/graph')

      .then((r) => r.json())

      .then(setGraph)

      .catch(console.error);

  }, [setGraph]);


  return (

    <main

      style={{

        display: 'grid',

        gridTemplateColumns: '240px 1fr 400px',

        gridTemplateRows: '64px 1fr 120px',

        height: '100vh',

        background: '#0B0F1A',

        color: 'white',

      }}

    >

      <header style={{ gridColumn: '1 / 4', borderBottom: '1px solid #1F2633', display: 'flex', alignItems: 'center', padding: '0 16px' }}>

        EQUILIBRIUM

      </header>


      <aside style={{ borderRight: '1px solid #1F2633', padding: 16 }}>

        <h3> Filtraciones </h3>

        Sitios, comunicaciones, estados </div>

      </aside>


      <section style={{ position: 'relative' }}>

        <EquilibriumCanvas />

      </section>


      <aside style={{ borderLeft: '1px solid #1F2633', padding: 16 }}>

        {!selectedNode & ' !selectedEdge && <div> Elige un nodo o una conexión </div}


        {selectedNode && (

          <>

            <h3>{selectedNode.data.label}</h3>

            Tip: {selectedNode.data.nodeType} </p>

            Estado: {selectedNode.data.state} </p>

            <pre style={{ whiteSpace: 'pre-wrap' }}>

              {JSON.stringify(selectedNode.data.metrics, null, 2)}

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

            <h3>{selectedEdge.data.label}</h3>

            Tip: {selectedEdge.data.edgeType} </p>

            Estado: {selectedEdge.data.state} </p>

            <p>Significado: {selectedEdge.data.meaning || '-'}</p>

            <p>Ética: {selectedEdge.data.ethics || '-'}</p>

          </>

        )}

      </aside>


      <footer style={{ gridColumn: '1 / 4', borderTop: '1px solid #1F2633', padding: 16 }}>

        Timeline / Simulation

      </footer>

    </main>

  );

}


Neo4j

El controlador oficial está conectado a través del Neo4j.driver_...; en la documentación también se recomienda que se indique claramente la base de referencia para la productividad. 

lib/neo4j.ts

import neo4j from 'neo4j-driver';


const uri = process.env.NEO4J_URI!;

const username = process.env.NEO4J_USERNAME!;

const password = process.env.NEO4J_PASSWORD!;

const database = process.env.NEO4J_DATABASE || 'neo4j';


export const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));


export { database };

API de lectura del grafo

app/api/graph/route.ts

import { NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function GET() {

  const query = `

    MATCH (n:EQNode)

    OPTIONAL MATCH (n)-[r:RELATES_TO]->(m:EQNode)

    RETURN collect(DISTINCT {

      id: n.id,

      type: n.nodeType,

      position: { x: n.x, y: n.y },

      data: {

        label: n.label,

        nodeType: n.nodeType,

        description: n.description,

        state: n.state,

        metrics: {

          trust: n.trust,

          activity: n.activity,

          load: n.load,

          influence: n.influence,

          stability: n.stability,

          ethics: n.ethics

        },

        tags: n.tags

      }

    }) AS nodes,

    collect(DISTINCT CASE WHEN r IS NULL THEN NULL ELSE {

      id: r.id,

      source: startNode(r).id,

      target: endNode(r).id,

      type: 'default',

      data: {

        label: r.label,

        edgeType: r.edgeType,

        meaning: r.meaning,

        process: r.process,

        norm: r.norm,

        metric: r.metric,

        ethics: r.ethics,

        intensity: r.intensity,

        risk: r.risk,

        state: r.state

      }

    } END) AS edges

  `;


  const result = await driver.executeQuery(query, {}, { database });

  const record = result.records[0];


  const nodes = record.get('nodes');

  const edges = record.get('edges').filter(Boolean);


  return NextResponse.json({ nodes, edges });

}

Crear un nodo

app/api/node/route.ts

import { NextRequest, NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function POST(req: NextRequest) {

  const body = await req.json();


  const query = `

    CREATE (n:EQNode {

      id: $id,

      label: $label,

      nodeType: $nodeType,

      description: $description,

      state: $state,

      x: $x,

      y: $y,

      trust: $trust,

      activity: $activity,

      load: $load,

      influence: $influence,

      stability: $stability,

      ethics: $ethics,

      tags: $tags

    })

    RETURN n

  `;


  await driver.executeQuery(

    query,

    {

      id: body.id,

      label: body.data.label,

      nodeType: body.data.nodeType,

      description: body.data.description || '',

      state: body.data.state,

      x: body.position.x,

      y: body.position.y,

      trust: body.data.metrics?.trust ?? null,

      activity: body.data.metrics?.activity ?? null,

      load: body.data.metrics?.load ?? null,

      influence: body.data.metrics?.influence ?? null,

      stability: body.data.metrics?.stability ?? null,

      ethics: body.data.metrics?.ethics ?? null,

      tags: body.data.tags ?? [],

    },

    { database }

  );


  return NextResponse.json({ ok: true });

}

Establecimiento de redes

app/api/edge/route.ts

import { NextRequest, NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function POST(req: NextRequest) {

  const body = await req.json();


  const query = `

    MATCH (a:EQNode {id: $source})

    MATCH (b:EQNode {id: $target})

    CREATE (a)-[r:RELATES_TO {

      id: $id,

      label: $label,

      edgeType: $edgeType,

      meaning: $meaning,

      process: $process,

      norm: $norm,

      metric: $metric,

      ethics: $ethics,

      intensity: $intensity,

      risk: $risk,

      state: $state

    }]->(b)

    RETURN r

  `;


  await driver.executeQuery(

    query,

    {

      id: body.id,

      source: body.source,

      target: body.target,

      label: body.data.label,

      edgeType: body.data.edgeType,

      meaning: body.data.meaning || '',

      process: body.data.process || '',

      norm: body.data.norm || '',

      metric: body.data.metric || '',

      ethics: body.data.ethics || '',

      intensity: body.data.intensity ?? null,

      risk: body.data.risk ?? null,

      state: body.data.state,

    },

    { database }

  );


  return NextResponse.json({ ok: true });

}

Cypher-scem

cypher/schema.cypher

CREATE CONSTRAINT eqnode_id IF NOT EXISTS

FOR (n:EQNode)REQUIRE n.id IS UNIQUE;

cypher/seed.cypher

CREATE (:EQNode {

  id: 'meaning-1',

  El label: & Desarrollo de competencias,

  nodeType: 'meaning',

  state: 'growth',

  x: 100,

  y: 120,

  ethics: 0.9

});


CREATE (:EQNode {

  id: 'actor-1',

  Label: "Cooperación FENIX,"

  nodeType: 'actor',

  state: 'harmony',

  x: 420,

  y: 180,

  trust: 0.82,

  activity: 0.77

});


CREATE (:EQNode {

  id: 'effect-1',

  El label: "Potencia de empleo,"

  nodeType: 'effect',

  state: 'growth',

  x: 760,

  y: 200,

  stability: 0.71

});


MATCH (a:EQNode {id: 'meaning-1'})

MATCH (b:EQNode {id: 'actor-1'})

CREATE (a)-[:RELATES_TO {

  id: 'edge-1',

  El label: " Intención ,"

  edgeType: 'intent',

  Meaning: "Ejecutar el contorno educativo."

  ethics: 'accesibilidad',

  state: 'growth',

  intensity: 0.8

}]->(b);


MATCH (a:EQNode {id: 'actor-1'})

MATCH (b:EQNode {id: 'effect-1'})

CREATE (a)-[:RELATES_TO {

  id: 'edge-2',

  label: "Retroalimentación,"

  edgeType: 'feedback',

  meaning: 'Evaluación del resultado del programa',

  metric: "Índice de empleo,"

  state: 'harmony',

  intensity: 0.7

}]->(b);

Ejemplo JSON para el frente

  "nodes": [

    {

      "id": "meaning-1",

      "type": "meaning",

      "position": { "x": 100, "y": 120 },

      "data": {

        "Label": "Desarrollo de competencias,"

        "nodeType": "meaning",

        "state": "growth",

        "metrics": { "ethics": 0.9 }

      }

    },

    {

      "id": "actor-1",

      "type": "actor",

      "position": { "x": 420, "y": 180 },

      "data": {

        "Label": "Cooperación FENIX,"

        "nodeType": "actor",

        "state": "harmony",

        "metrics": { "trust": 0.82, "activity": 0.77 }

      }

    }

  ],

  "edges": [

    {

      "id": "edge-1",

      "source": "meaning-1",

      "target": "actor-1",

      "type": "default",

      "data": {

        "Label," "Intención,"

        "edgeType": "intent",

        "Meaning": "El lanzamiento de un contorno educativo,"

        "state": "growth",

        "intensity": 0.8

      }

    }

  ]

}

¿Qué hacemos con los hipérboles de verdad?

Este es un momento importante. React Flow trabaja con los edges normales entre dos nudos. Es su modelo de base como un flowgraph UI. Así que para MVP, haz lo siguiente:

Opción A - práctica

  • guardar el hiperrebro como un nodo separado del tipo de proceso o protocolo
  • Conectar a los participantes con los edges normales

Es decir:

Meaning -> ProtocolHub -> Actor

Resource -> ProtocolHub

Norm -> ProtocolHub

ProtocolHub -> Effect

Es una decisión justa. No rompe la UI y le permite mostrar una conexión multidimensional.

Tipo para esto.

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol';

Y entonces el hiperrrebro a nivel de datos es un protocol-relator con un conjunto de relaciones entrantes o en curso. Es el mejor comienzo.

Primer escenario de trabajo

No te arruines. Haz un guión:

Hipótesis: Escuela Cooperativa

Espejos:

  • Significado: desarrollo de competencias
  • Entidad: Cooperativa
  • Recursos: Fondo
  • Norma: Reglamento
  • Protocolo: programa educativo
  • Efecto: crecimiento del empleo

Relaciones:

  • meaning → protocol
  • actor → protocol
  • resource → protocol
  • norm → protocol
  • protocol → effect

Este es el MVP que se puede mostrar.

Comandos de lanzamiento

package.json

dependencia

{

  "dependencies": {

    "@xyflow/react": "^12",

    "neo4j-driver": "^6",

    "next": "^15",

    "react": "^19",

    "react-dom": "^19",

    "zustand": "^5"

  },

  "devDependencies": {

    "@types/node": "^22",

    "@types/react": "^19",

    "typescript": "^5"

  }

}

El paquete @xyflow/react ° es un nombre pertinente para React Flow, y Neo4j JS driver está en la rama 6.x. 

.env.local

NEO4J_URI=neo4j://localhost

NEO4J_USERNAME=neo4j

NEO4J_PASSWORD=your_password

NEO4J_DATABASE=neo4j

¿Qué tendrás después de eso?

Una vez que se ponga en marcha, tendrás:

  • grafo interactivo,
  • Nodos de casta,
  • Detective Inspector de la derecha,
  • cargando datos de Neo4j,
  • Creación de nodos y conexiones a través de API;
  • La base del guión, diagnóstico y simulación.

Ya no es un concepto. Es un marco de productos vivo.


A continuación le doy el marco del sistema que ya se ha reunido: qué es cada módulo, qué es lo que necesitan los nudos de casta, cómo construir un conector interactivo, y el código que puede colocarse inmediatamente en MVP. Para React Flow, el paquete actual @xyflow/react; los nudos de casta se hacen como componentes de React, se transmiten a través de nodeTypes y los puntos de conexión se establecen a través de <Handle />. Para Neo4j, ahora es normal utilizar un controlador oficial JS y driver.executeQuery, con parámetros e indicaciones claras de la base. 

Definiciones de los módulos del sistema

Recomendamos que se registren 10 módulos para la primera versión de trabajo.

Graph Core

El corazón del sistema. Guarda los nodos, las comunicaciones, la selección del usuario, los modos, los filtros, layout y los eventos de la interfaz. Lo que hace:

  • mantiene el estado actual del grafo
  • Actualiza los nodos y las comunicaciones
  • informa UI que está seleccionado
  • activa el nuevo dibujo

Node Registry

Registro de tipos de nodos. Lo que hace:

  • vincula node.type → componente de React
  • Prepara cómo reventar meaning, actor, resource, protocol, etc.
  • permite ampliar el sistema sin reescribir el lienzo

Edge Registry. Registro de comunicaciones. Lo que hace:

  • define el visual y el comportamiento de las relaciones
  • crea estilos para intent, cooperation, feedback, evolution
  • Más adelante puede incluir costillas de casta con firmas e indicadores de riesgo

Protocol Engine

Este es el módulo clave EQUILIBRIUM. Lo que hace:

  • Recoge un efecto multidimensional de varias cosas
  • En realidad, aplica el hiperrrebro como un protocolo separado.
  • Conecta el sentido, los actores, los recursos, las normas y los efectos en un solo contorno

Para MVP es la mejor manera, porque el React Flow por naturaleza funciona con costillas normales entre dos nudos; es más fácil imaginar una conexión multidimensional como un nodo de hab. 

Inspector Panel. El panel derecho. Lo que hace:

  • muestra el pasaporte del nodo seleccionado
  • muestra el pasaporte de la conexión seleccionada
  • permite editar atributos
  • Muestra métricas, riesgos, ética, carga

Filter & Views. El panel izquierdo. Lo que hace:

  • filtra por tipo de nodo
  • filtra en posición
  • filtra por tipo de conexión
  • cambia los regímenes: visión, diagnóstico, guión, acción

Timeline & Events. El panel inferior. Lo que hace:

  • Muestra eventos en el tiempo
  • permite reproducir el estado del grafo
  • Simulación de escenarios en el futuro

Diagnostics Engine. Diagnóstico del sistema. Lo que hace:

  • Busca nudos sobrecargados
  • Encuentra relaciones de conflicto.
  • Pone de relieve las zonas de crisis
  • Calcula los agujeros en el contorno: por ejemplo, hay un proceso, pero no una norma.

Persistence Layer. La capa de almacenamiento... Lo que hace:

  • lee y escribe el grafo en Neo4j
  • guarda las posiciones de los nodos
  • guarda métricas y estados
  • entrega JSON al frente.

AI/Rule Layer. Una serie de recomendaciones. Lo que hace:

  • ofrece nuevos vínculos
  • Advertencia sobre los riesgos
  • Piensa en los euristicos simples.
  • explica por qué el sistema marca el nodo como una crisis

Tipos de nodos del sistema

No se necesitan 30 tipos para empezar. Necesito 7.

export type EQNodeType =

  'meaning' / / Significado

  ○ 'actor' / / sujeto

  * Resource / / recurso

  ○ 'norm' // norma

  * 'effect' / / effect

  ○ 'protocol' // hiperrebro como nudo hab

  ○ 'metric'; / / metric


Meaning. El propósito, la misión, el valor. Responde a la pregunta: ¿Por qué?

Actor. Hombre, organización, agente, cooperativa. Responde a la pregunta:

Resource. Dinero, fondos, datos, energía, tiempo. Responde a la pregunta: ¿De qué se trata?

Norma. Reglamento, regla, contrato, ley. Responde a la pregunta: ¿cuáles son las restricciones?

Effect result. Responde a la pregunta: ¿Qué ha pasado?

Protocol. Módulo de trabajo central. Esta es la aplicación de una conexión multidimensional. Reúne a las otras criaturas alrededor de él.

Metric. Medidor de resultados: índice de confianza, índice de sostenibilidad, índice de empleo.

Tipos de vínculos

export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';

Significado:

  • Intent ○ El sentido está en el protocolo
  • input ○ recurso o actor participa en el protocolo
  • la norma limita el protocolo
  • output ○ El protocolo produce efecto
  • El efecto feedback es un efecto de retorno.
  • evolution ○ cambios en el tiempo

Tipos de datos básicos

types/equilibrium.ts

export type EQState =

  | 'harmony'

  | 'tension'

  | 'crisis'

  | 'growth'

  | 'transformation'

  | 'unknown';


export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol'

  | 'metric';


export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';


export interface EQMetrics {

  trust?: number;

  load?: number;

  activity?: number;

  influence?: number;

  stability?: number;

  ethics?: number;

  value?: number;

}


export interface EQNodeData {

  label: string;

  nodeType: EQNodeType;

  description?: string;

  state: EQState;

  metrics: EQMetrics;

  tags?: string[];

}


export interface EQNode {

  id: string;

  type: EQNodeType;

  position: { x: number; y: number };

  data: EQNodeData;

}


export interface EQEdgeData {

  label: string;

  edgeType: EQEdgeType;

  state: EQState;

  meaning?: string;

  risk?: number;

  intensity?: number;

}


export interface EQEdge {

  id: string;

  source: string;

  target: string;

  type?: string;

  data: EQEdgeData;

}


export interface EQGraph {

  nodes: EQNode[];

  edges: EQEdge[];

}

Nodos de casco: descripción

En React Flow, el nudo de la casta es un simple React-componente que recibe NodeProps. Se puede hacer con varios Handle, controles interactivos y su propia UI. Es un modelo que se apoya oficialmente. Abajo, un conjunto de nodos que ya son como un sistema.

MeaningNode. Un nudo de palabras. Por lo general, una pequeña, brillante, con una entrada y una salida.

ActorNode. El núcleo del sudes. Muestra confianza, actividad y presión.

ResourceNode. Un nudo de recursos. Generalmente compacta, con un índice de accesibilidad.

Norma Node. Un nodo normativo. Muestra el tipo de restricción.

EffectNode. Un punto de resultado. Muestra influencia y resistencia.

MetricNode. Un centro de medición. Se utiliza como indicador de estimación.

ProtocolNode. Lo más importante. Hub Central. Tiene varios puntos de conexión:

  • a la izquierda
  • De arriba a arriba de los actores
  • recursos de abajo abajo
  • efectos a la derecha
  • Handle para las normas
  • Handle para metric

Contenedor de base común

components/graph/nodes/NodeShell.tsx

'use client';


import { ReactNode } from 'react';


type Props = {

  title: string;

  subtitle: string;

  children?: ReactNode;

  glow?: string;

};


export default function NodeShell({ title, subtitle, children, glow }: Props) {

  return (

    <div

      style={{

        minWidth: 190,

        borderRadius: 18,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: glow || '0 0 18px rgba(255,255,255,0.08)',

        color: '#fff',

      }}

    >

      <div

        className="drag-handle"

        style={{

          fontSize: 11,

          opacity: 0.65,

          marginBottom: 6,

          cursor: 'grab',

        }}

      >

        {subtitle}

      </div>

      <div style={{ fontWeight: 700, fontSize: 15 }}>{title}</div>

      <div style={{ marginTop: 10 }}>{children}</div>

    </div>


Umbrales: código

MeaningNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function MeaningNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      Subtitle="El significado"

      glow="0 0 24px rgba(123, 97, 255, 0.24)"

    >

      <div style={{ fontSize: 12, opacity: 0.85 }}>

        Ética: {data.metrics?.ethics?? '-'}

      </div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

ActorNode.tsx


'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function ActorNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle= "Sujeto"

      glow="0 0 24px rgba(77, 163, 255, 0.20)"

    >

      <div style={{{fontSize: 12 }}>Confianza: {data.metrics?.trust ?? '-}}>

      {div style={{{fontSize: 12 }}>Emergencia: {data.metrics?.activity ??? '-'}} </div>

      {div style={{{fontSize: 12 }}}Gaso: {data.metrics?.load ??? '-'}}}/div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

ResourceNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function ResourceNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle=Resurs

      glow="0 0 24px rgba(255, 200, 87, 0.18)"

    >

      <div style={{{fontSize: 12 }}>Acceso: {data.metrics?.value ??? '-'}}}/div>

      <div style={{{fontSize: 12 }}>Estabilidad: {data.metrics?.stability ?? '-'}} </div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

NormNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function NormNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle= "Norma"

      glow="0 0 24px rgba(255,255,255,0.12)"

    >

      <div style={{ fontSize: 12 }}>{data.description || 'Regla / restricción'}</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

EffectNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function EffectNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle= "Effect"

      glow="0 0 24px rgba(61, 220, 151, 0.22)"

    >

      <div style={{{ fontSize: 12 }}}Influence ? '-} </div>

      <div style={{{fontSize: 12 }}}>Sostenibilidad: {data.metrics?.stability ?? '-'}} </div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

MetricNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function MetricNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle=Metrical

      glow="0 0 24px rgba(80, 180, 255, 0.16)"

    >

      <div style={{{fontSize: 12 }}}Cignidad: {data.metrics?.value ?? '-'}}}/div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

ProtocolNode.tsx. Es el nudo principal.

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function ProtocolNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 260,

        borderRadius: 20,

        padding: 14,

        background: '#161d2d',

        border: '1px solid #2a3550',

        boxShadow: '0 0 28px rgba(123, 97, 255, 0.28)',

        color: '#fff',

      }}

    >

      <div className="drag-handle" style={{ fontSize: 11, opacity: 0.65, marginBottom: 6 }}>

        Protocolo / Hiperrebro

      </div>


      <div style={{ fontSize: 16, fontWeight: 800 }}>{data.label}</div>


      <div style={{ marginTop: 10, fontSize: 12, opacity: 0.9 }}>

        {data.description || 'Sistema multidimensional de acción'}

      </div>


      <div

        style={{

          display: 'grid',

          gridTemplateColumns: '1fr 1fr',

          gap: 6,

          marginTop: 10,

          fontSize: 12,

        }}

      >

        Etica: {data.metrics?.ethics ?? '-'}} </div>

        Cargando: {data.metrics?.load ?? '-'}} </div>

        <div>Estabilidad: {data.metrics?.stability ?? '-'}} </div>

        Activo: {data.metrics?.activity ?? '-'}} </div>

      </div>


      <Handle type="target" position={Position.Left} id="meaning" style={{ top: '20%' }} />

      <Handle type="target" position={Position.Top} id="actors" style={{ left: '35%' }} />

      <Handle type="target" position={Position.Bottom} id="resources" style={{ left: '35%' }} />

      <Handle type="target" position={Position.Left} id="norms" style={{ top: '75%' }} />


      <Handle type="source" position={Position.Right} id="effects" style={{ top: '35%' }} />

      <Handle type="source" position={Position.Right} id="metrics" style={{ top: '75%' }} />

    </div>

  );


El eje de varios Handle coincide plenamente con el modelo React Flow, donde los nudos de casta pueden tener el mayor número posible de puntos de connection source/target. 

Registro de nudos de castas

components/graph/nodes/index.ts

import MeaningNode from './MeaningNode';

import ActorNode from './ActorNode';

import ResourceNode from './ResourceNode';

import NormNode from './NormNode';

import EffectNode from './EffectNode';

import MetricNode from './MetricNode';

import ProtocolNode from './ProtocolNode';


export const nodeTypes = {

  meaning: MeaningNode,

  actor: ActorNode,

  resource: ResourceNode,

  norm: NormNode,

  effect: EffectNode,

  metric: MetricNode,

  protocol: ProtocolNode,

};

Columna interactiva: código de lienzo

<ReactFlow />El componente principal que renderiza los nudos y las costillas, procesa las interacciones y trabaja con el estado. Es el punto central oficial de la biblioteca. 

components/graph/EquilibriumCanvas.tsx


'use client';


import { useEffect, useMemo, useState } from 'react';

import {

  ReactFlow,

  ReactFlowProvider,

  Background,

  MiniMap,

  Controls,

  addEdge,

  useNodesState,

  useEdgesState,

  Connection,

  Edge,

  NodeMouseHandler,

  EdgeMouseHandler,

  BackgroundVariant,

} from '@xyflow/react';

import '@xyflow/react/dist/style.css';


import { nodeTypes } from './nodes';

import { EQGraph } from '@/types/equilibrium';


type Props = {

  graph: EQGraph;

  onSelectNode?: (node: any) => void;

  onSelectEdge?: (edge: any) => void;

};


function edgeStyleByState(state?: string) {

  switch (state) {

    case 'growth':

      return { stroke: '#4DA3FF', strokeWidth: 2.5 };

    case 'crisis':

      return { stroke: '#FF4D4F', strokeWidth: 2.5 };

    case 'harmony':

      return { stroke: '#3DDC97', strokeWidth: 2.5 };

    case 'tension':

      return { stroke: '#FFC857', strokeWidth: 2.5 };

    default:

      return { stroke: '#76839a', strokeWidth: 1.8 };

  }

}


function CanvasInner({ graph, onSelectNode, onSelectEdge }: Props) {

  const initialNodes = useMemo(() => graph.nodes, [graph.nodes]);


  const initialEdges = useMemo(

    () =>

      graph.edges.map((e) => ({

        ...e,

        animated: e.data.state === 'growth' || e.data.edgeType === 'feedback',

        style: edgeStyleByState(e.data.state),

        label: e.data.label,

      })),

    [graph.edges]

  );


  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges as Edge[]);

  const [selectedId, setSelectedId] = useState<string | null>(null);


  useEffect(() => setNodes(initialNodes), [initialNodes, setNodes]);

  useEffect(() => setEdges(initialEdges as Edge[]), [initialEdges, setEdges]);


  const onConnect = (connection: Connection) => {

    setEdges((eds) =>

      addEdge(

        {

          ...connection,

          type: 'smoothstep',

          data: {

            El label: " Nueva comunicación ,"

            edgeType: 'input',

            state: 'unknown',

          },

          style: edgeStyleByState('unknown'),

        },

        eds

      )

    );

  };


  const handleNodeClick: NodeMouseHandler = (_, node) => {

    setSelectedId(node.id);

    onSelectNode?.(node);

  };


  const handleEdgeClick: EdgeMouseHandler = (_, edge) => {

    setSelectedId(edge.id);

    onSelectEdge?.(edge);

  };


  return (

    <div style={{ width: '100%', height: '100%' }}>

      <ReactFlow

        nodes={nodes.map((n) => ({

          ...n,

          selected: n.id === selectedId,

          dragHandle: '.drag-handle',

        }))}

        edges={edges}

        nodeTypes={nodeTypes}

        onNodesChange={onNodesChange}

        onEdgesChange={onEdgesChange}

        onConnect={onConnect}

        onNodeClick={handleNodeClick}

        onEdgeClick={handleEdgeClick}

        fitView

      >

        <MiniMap />

        <Controls />

        <Background variant={BackgroundVariant.Dots} gap={20} size={1} />

      </ReactFlow>

    </div>

  );

}


export default function EquilibriumCanvas(props: Props) {

  return (

    <ReactFlowProvider>

      <CanvasInner {...props} />

    </ReactFlowProvider>

  );

}

Ejemplo de pantalla interactiva

app/page.tsx

'use client';


import { useEffect, useState } from 'react';

import EquilibriumCanvas from '@/components/graph/EquilibriumCanvas';

import { EQGraph } from '@/types/equilibrium';


export default function HomePage() {

  const [graph, setGraph] = useState<EQGraph>({ nodes: [], edges: [] });

  const [selectedNode, setSelectedNode] = useState<any>(null);

  const [selectedEdge, setSelectedEdge] = useState<any>(null);


  useEffect(() => {

    fetch('/api/graph')

      .then((r) => r.json())

      .then(setGraph)

      .catch(console.error);

  }, []);


  return (

    <main

      style={{

        display: 'grid',

        gridTemplateColumns: '260px 1fr 400px',

        gridTemplateRows: '64px 1fr 120px',

        height: '100vh',

        background: '#0B0F1A',

        color: '#fff',

      }}

    >

      <header

        style={{

          gridColumn: '1 / 4',

          borderBottom: '1px solid #1F2633',

          display: 'flex',

          alignItems: 'center',

          padding: '0 16px',

          fontWeight: 800,

        }}

      >

        EQUILIBRIUM

      </header>


      <aside style={{ borderRight: '1px solid #1F2633', padding: 16 }}>

        <h3> Filtraciones </h3>

        Títulos de los nodos </div>

        <div>Estados</div>

        Tipos de comunicación </div>

      </aside>


      <section style={{ position: 'relative' }}>

        <EquilibriumCanvas

          graph={graph}

          onSelectNode={(node) => {

            setSelectedEdge(null);

            setSelectedNode(node);

          }}

          onSelectEdge={(edge) => {

            setSelectedNode(null);

            setSelectedEdge(edge);

          }}

        />

      </section>


      <aside style={{ borderLeft: '1px solid #1F2633', padding: 16, overflow: 'auto' }}>

        {!selectedNode & & & !selectedEdge && <div> Seleccione un nodo o una conexión </div>}


        {selectedNode && (

          <>

            <h3>{selectedNode.data.label}</h3>

            <div> Tip: {selectedNode.data.nodeType} </div>

            Estado: {selectedNode.data.state} </div>

            <div style={{ marginTop: 12, fontSize: 13, opacity: 0.9 }}>

              {selectedNode.data.description || 'Sin descripción'}

            </div>

            <pre style={{ marginTop: 12, fontSize: 12, whiteSpace: 'pre-wrap' }}>

              {JSON.stringify(selectedNode.data.metrics, null, 2)}

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

            <h3>{selectedEdge.data.label}</h3>

            <div> Tip: {selectedEdge.data.edgeType} </div>

            Estado: {selectedEdge.data.state} </div>

            <div>Significado: {selectedEdge.data.meaning || '-'}</div>

            <div>Risk: {selectedEdge.data.risk ?? '-'}} <>

            Intensidad: {selectedEdge.data.intensity ? '-'} <}}>

          </>

        )}

      </aside>


      <footer

        style={{

          gridColumn: '1 / 4',

          borderTop: '1px solid #1F2633',

          padding: 16,

        }}

      >

        Timeline / Events / Simulation

      </footer>

    </main>

  );

}


Ejemplo de barra para EQUILIBRIUM. No es una abstracción, es un guión de trabajo.

lib/mockGraph.ts

import { EQGraph } from '@/types/equilibrium';


export const mockGraph: EQGraph = {

  nodes: [

    {

      id: 'meaning-1',

      type: 'meaning',

      position: { x: 60, y: 220 },

      data: {

        El label: & Desarrollo de competencias,

        nodeType: 'meaning',

        description: "Rostro del potencial humano de la región,"

        state: 'growth',

        metrics: { ethics: 0.92 },

      },

    },

    {

      id: 'actor-1',

      type: 'actor',

      position: { x: 280, y: 60 },

      data: {

        Label: "Cooperación FENIX,"

        nodeType: 'actor',

        state: 'harmony',

        metrics: { trust: 0.82, activity: 0.77, load: 0.42 },

      },

    },

    {

      id: 'resource-1',

      type: 'resource',

      position: { x: 280, y: 360 },

      data: {

        Label: "Fondation d'economique,"

        nodeType: 'resource',

        state: 'harmony',

        metrics: { value: 0.76, stability: 0.66 },

      },

    },

    {

      id: 'norm-1',

      type: 'norm',

      position: { x: 280, y: 500 },

      data: {

        El label: " Regla del programa ,"

        nodeType: 'norm',

        description: "La transparencia de la selección y la accesibilidad de la participación,"

        state: 'harmony',

        metrics: {},

      },

    },

    {

      id: 'protocol-1',

      type: 'protocol',

      position: { x: 640, y: 230 },

      data: {

        El label: "Protocolo de la formación cooperativa,"

        nodeType: 'protocol',

        description: 'Contorno: aprendizaje de participación de empleo',

        state: 'growth',

        metrics: {

          ethics: 0.9,

          load: 0.58,

          stability: 0.71,

          activity: 0.79,

        },

      },

    },

    {

      id: 'effect-1',

      type: 'effect',

      position: { x: 1020, y: 180 },

      data: {

        El label: "Potencia de empleo,"

        nodeType: 'effect',

        state: 'growth',

        metrics: { influence: 0.74, stability: 0.69 },

      },

    },

    {

      id: 'metric-1',

      type: 'metric',

      position: { x: 1020, y: 340 },

      data: {

        Label: 'Índice de empleo',

        nodeType: 'metric',

        state: 'harmony',

        metrics: { value: 0.63 },

      },

    },

  ],

  edges: [

    {

      id: 'e1',

      source: 'meaning-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        El label: " Intención ,"

        edgeType: 'intent',

        state: 'growth',

        Meaning: "El punto está en el protocolo."

        intensity: 0.88,

      },

    },

    {

      id: 'e2',

      source: 'actor-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Participación',

        edgeType: 'input',

        state: 'harmony',

        Meaning: "El sudes está cumpliendo el protocolo,"

        intensity: 0.72,

      },

    },

    {

      id: 'e3',

      source: 'resource-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Resurs',

        edgeType: 'input',

        state: 'harmony',

        intensity: 0.68,

      },

    },

    {

      id: 'e4',

      source: 'norm-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        El label: " Limitación,"

        edgeType: 'constraint',

        state: 'harmony',

        meaning: "La norma limita y guía,"

      },

    },

    {

      id: 'e5',

      source: 'protocol-1',

      target: 'effect-1',

      type: 'smoothstep',

      data: {

        label: 'Resultat',

        edgeType: 'output',

        state: 'growth',

        intensity: 0.81,

      },

    },

    {

      id: 'e6',

      source: 'protocol-1',

      target: 'metric-1',

      type: 'smoothstep',

      data: {

        El label: " Medición ,"

        edgeType: 'output',

        state: 'harmony',

      },

    },

    {

      id: 'e7',

      source: 'effect-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: "Retroalimentación,"

        edgeType: 'feedback',

        state: 'harmony',

        risk: 0.21,

      },

    },

  ],

};

Neo4j: plan de almacenamiento

La JS oficial driver Neo4j recomienda que se utilicen las consultas predeterminadas, mientras que el executeQuery es el que crea la transacción. También es útil siempre indicar claramente la base de datos para reducir el exceso de overhead. 

lib/neo4j.ts

import neo4j from 'neo4j-driver';


const uri = process.env.NEO4J_URI!;

const username = process.env.NEO4J_USERNAME!;

const password = process.env.NEO4J_PASSWORD!;

const database = process.env.NEO4J_DATABASE || 'neo4j';


export const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));

export { database };

cypher/schema.cypher

CREATE CONSTRAINT eqnode_id IF NOT EXISTS

FOR (n:EQNode)

REQUIRE n.id IS UNIQUE;

app/api/graph/route.ts

import { NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function GET() {

  const query = `

    MATCH (n:EQNode)

    OPTIONAL MATCH (n)-[r:RELATES_TO]->(m:EQNode)

    RETURN

      collect(DISTINCT {

        id: n.id,

        type: n.nodeType,

        position: { x: n.x, y: n.y },

        data: {

          label: n.label,

          nodeType: n.nodeType,

          description: n.description,

          state: n.state,

          metrics: {

            trust: n.trust,

            load: n.load,

            activity: n.activity,

            influence: n.influence,

            stability: n.stability,

            ethics: n.ethics,

            value: n.value

          }

        }

      }) AS nodes,

      collect(DISTINCT CASE WHEN r IS NULL THEN NULL ELSE {

        id: r.id,

        source: startNode(r).id,

        target: endNode(r).id,

        type: 'smoothstep',

        data: {

          label: r.label,

          edgeType: r.edgeType,

          state: r.state,

          meaning: r.meaning,

          risk: r.risk,

          intensity: r.intensity

        }

      } END) AS edges

  `;


  const result = await driver.executeQuery(query, {}, { database });

  const record = result.records[0];


  const nodes = record.get('nodes');

  const edges = record.get('edges').filter(Boolean);


  return NextResponse.json({ nodes, edges });

}


Diagnóstico: reglas básicas. No es una I.I. es una lógica dura. Tienes que empezar con ella.

lib/diagnostics.ts

import { EQGraph } from '@/types/equilibrium';


export interface DiagnosticItem {

  type: 'warning' | 'error' | 'info';

  targetId: string;

  message: string;

}


export function runDiagnostics(graph: EQGraph): DiagnosticItem[] {

  const result: DiagnosticItem[] = [];


  for (const node of graph.nodes) {

    if (node.type === 'protocol') {

      const incoming = graph.edges.filter((e) => e.target === node.id);

      const outgoing = graph.edges.filter((e) => e.source === node.id);


      const hasMeaning = incoming.some((e) => e.data.edgeType === 'intent');

      const hasActor = incoming.some((e) => e.source.startsWith('actor-'));

      const hasResource = incoming.some((e) => e.source.startsWith('resource-'));

      const hasNorm = incoming.some((e) => e.data.edgeType === 'constraint');

      const hasOutput = outgoing.some((e) => e.data.edgeType === 'output');


      if (!hasMeaning) {

        result.push({

          type: 'error',

          targetId: node.id,

          message: "El protocolo no tiene ningún sentido,"

        });

      }


      if (!hasActor) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: "El protocolo no tiene entidad de ejecución,"

        });

      }


      if (!hasResource) {

        result.push({

          type: 'warning',

          targetId: node.id,

          Message: "El protocolo no tiene entrada de recursos,"

        });

      }


      if (!hasNorm) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: "El protocolo no tiene límites normativos,"

        });

      }


      if (!hasOutput) {

        result.push({

          type: 'error',

          targetId: node.id,

          message: "El protocolo no tiene resultado,"

        });

      }


      if ((node.data.metrics.load ?? 0) > 0.85) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'Protocolo sobrecargado',

        });

      }

    }

  }


  return result;

}

¿Qué es el gráfico interactivo de la página web?

No solo un lienzo de nudos, sino 7 funciones:

  • Drag
    Mueve los nodos.
  • Connect
    Crear una nueva conexión a través de las manos.
  • Select
    Seleccione un nudo o una conexión.
  • Inspect
    Muestra el contenido a la derecha.
  • Filter
    Dejar en pantalla los tipos correctos.
  • Diagnose
    Resaltar errores y riesgos.
  • Evolve
    Añadir nuevo protocolo, recurso, norma, metrología.

React Flow es precisamente para editores node-basados y gráficos interactivos; la documentación describe por separado la castamación de nudos, handles, theming y ejemplos. 


¿Qué más hay que añadir a la versión 2?

No hay decoraciones. Se necesitan los siguientes módulos:

  • LeftFilters.tsx
  • RightInspector.tsx
  • BottomTimeline.tsx
  • CreateProtocolModal.tsx
  • DiagnosticsPanel.tsx

Y las siguientes mejoras:

  • Mantener las posiciones de los nodos en el BD
  • forma de creación del protocolo
  • edición de metric
  • layout algoritmo
  • Iluminación de los vínculos de conflicto
  • SSR-safe configuración si hay un render de servidores; en React Flow para SSR hay un NodeHandle por separado cuando no se ha medido DOM.  

Cómo conseguir esto en MVP

Vidrio mínimo:

  • Next.js
  • TypeScript
  • @xyflow/react
  • Neo4j
  • Zustand

Aspectos mínimos:

  • 7 tipos de nudos
  • 6 tipos de conexiones
  • 1
  • 1 Panel derecho
  • 1
  • 1

Hipótesis mínima

  • Significado
  • sudes
  • Recursos
  • norma
  • Protocolo
  • efecto
  • metrica

Esto va a parecer un verdadero panel de operaciones, no un esquema para la presentación.

La conclusión arquitectónica más importante

El hiperrrebro de EQUILIBRIUM no tiene por qué tratar de dibujar por la fuerza como superlinea mágica.

Para una interfaz de trabajo, tienes que hacerlo como un protocol-relator que reúne a todos los participantes en el proceso.

Esta decisión es la siguiente:

  • No rompe React Flow,
  • Se encuentra en Neo4j,
  • Se diagnostica fácilmente,
  • se expande fácilmente,
  • Se muestra bien en la UI.

Y es la manera más saludable de hacer que el sistema sea real.

A continuación, es lógico reunir un conjunto completo de archivos del proyecto: package.json, tsconfig, app/page.tsx, todos los componentes node, API rowts, seed para Neo4j y CSS, para que pueda simplemente insertarse en el repositorio y lanzarlo.