De sueño a grafo: lo que aprendí modelando metas, progreso y colaboración con React Flow y Three.js
Cómo construí Praxis: un único árbol de objetivos renderizado como lista, mapa 2D y constelación 3D, con progreso derivado, prevención de ciclos, colaboración e IA opcional.
- react flow
- three.js
- supabase
- grafos
- producto digital
La interfaz bonita solo funcionaba porque el modelo debajo era aburrido y consistente
Praxis partió de una idea visual: transformar un objetivo grande en un árbol donde las relaciones fueran visibles y no una checklist plana.
El mismo contenido puede verse como lista, mapa 2D y constelación 3D.
La parte difícil no era Three.js. Era garantizar que todas esas interfaces representaran un solo grafo confiable y no tres verdades diferentes.
Una meta no es una pantalla; es un nodo
Modelé el dominio antes que las visualizaciones.
type GoalNode = {
id: string
ownerId: string
parentId: string | null
title: string
status: 'planned' | 'active' | 'done'
weight: number
position2d?: { x: number; y: number }
}
El mismo nodo puede aparecer en tabla, canvas o escena 3D. La UI no se vuelve el modelo de datos.
Progreso es dato derivado, no un campo que todos deberían editar
Si padre e hijos pueden editar progreso manualmente, aparecen contradicciones.
Cuando la relación lo permite, prefiero derivarlo.
const progress = children.reduce(
(sum, child) => sum + child.progress * child.weight,
0
) / children.reduce((sum, child) => sum + child.weight, 0)
La fórmula exacta es una decisión de producto. El principio es evitar dos fuentes de verdad para el mismo hecho.
Mover un nodo puede corromper el árbol
Drag-and-drop parece visual, pero cambiar de padre es una operación de dominio.
Si A se vuelve hijo de uno de sus descendientes, creamos un ciclo.
function canMove(nodeId: string, newParentId: string | null) {
if (!newParentId) return true
if (nodeId === newParentId) return false
return !isDescendant(nodeId, newParentId)
}
React Flow dibuja la conexión. La aplicación decide si es válida.
React Flow resolvió interacción, no regla de negocio
Pan, zoom, nodes y edges encajan muy bien en React Flow.
Pero las invariantes quedaron fuera del componente. La UI propone; la aplicación valida; el grafo persistido es el resultado.
Así las mismas reglas pueden usarse desde API, móvil o importación futura.
Posicionamiento automático y manual pueden coexistir
Un layout automático ayuda a empezar. Pero después de que el usuario organiza su mapa, el sistema no debería pisar ese trabajo.
type PositionMode = 'auto' | 'manual'
Distinguir posición sugerida de posición fijada hace que la herramienta se sienta colaborativa en vez de obstinada.
La constelación 3D no necesitaba convertirse en otro producto
Reutilicé el mismo grafo y lo proyecté en coordenadas 3D. Un layout estilo Fibonacci sphere puede distribuir nodos alrededor de una esfera sin guardar manualmente cada punto.
grafo canónico
├─ proyección lista
├─ proyección 2D
└─ proyección 3D
Las coordenadas 3D son presentación, no una segunda jerarquía.
Cargar Three.js bajo demanda protege la experiencia principal
La visualización 3D pesa más que una lista.
Quien solo entra a revisar tareas de hoy no debería pagar el costo de la constelación en cada ruta.
Trato 3D como una capability cargada cuando se solicita. Una feature llamativa no debería penalizar los flujos cotidianos.
Colaboración realtime es sincronización más autorización
Supabase Realtime puede propagar cambios rápido, pero también necesito saber quién puede ver y modificar cada nodo.
Un evento realtime no puede ser atajo para saltar ownership.
Las mismas reglas de acceso deben proteger consultas, subscriptions y mutations.
Presencia y colaboración no son lo mismo
Mostrar quién está online es presence. Persistir una edición es sincronización. Resolver dos ediciones simultáneas es política de conflicto.
Separar estos conceptos evita construir un editor colaborativo completo cuando el producto solo necesita coordinación ligera.
WOOP y planes "si-entonces" entran como método, no como autoridad de IA
WOOP—Wish, Outcome, Obstacle, Plan—y las implementation intentions ayudan a estructurar reflexión.
La IA puede convertir una idea vaga en alternativas más concretas, pero la persona sigue siendo dueña de su objetivo.
Me gusta usar IA así en productividad: estructurar pensamiento sin apropiarse de la decisión.
IA opcional es una decisión de producto importante
Crear metas, jerarquía, progreso y visualización debe seguir funcionando sin un modelo disponible.
Eso aumenta resiliencia y respeta a quien no quiere IA dentro de su planificación personal.
La IA es enhancement, no dependencia básica del dominio.
La suscripción también es estado de dominio
Un plan pago no debería ser solo un plan === 'pro' repetido en componentes.
Prefiero capabilities explícitas:
type Capability =
| 'advanced_views'
| 'collaboration'
| 'ai_coach'
Así pricing puede cambiar sin reescribir la lógica de todo el producto.
El insight mayor: un buen modelo permite muchas interfaces sin duplicar verdad
Cuando el grafo es confiable:
- lista es una proyección;
- mapa es otra;
- 3D es otra;
- analytics de progreso es otra.
Cuando el modelado está mal, cada pantalla crea un estado nuevo que sincronizar.
La constelación 3D llama la atención en la demo. Lo que hace que el producto siga funcionando después es mucho menos glamuroso: reglas de padre e hijo, ciclos, permisos y progreso derivado.
Ese contraste resume mi forma de ver ingeniería de producto: el visual puede ser ambicioso; la fuente de verdad debe ser tediosamente confiable.