Colaboración en tiempo real
Colaboración en tiempo real
Una nueva arquitectura de colaboración
La mayoría de las herramientas de colaboración en tiempo real (Figma, Google Docs, Notion) usan una arquitectura centralizada: todos los cambios pasan por un servidor que actúa como árbitro de la verdad. Este modelo es confiable y familiar, pero tiene tres limitaciones importantes:
- Dependencia del servidor: Si el servidor cae, la colaboración se detiene
- Privacidad de los datos: Todo el contenido pasa por servidores de terceros
- Costo de infraestructura: Los proveedores cobran por la colaboración en tiempo real
OpenPencil elige un camino diferente: colaboración P2P (peer-to-peer). Los participantes se conectan directamente entre sí, sin servidor intermediario. Los cambios se propagan directamente de navegador a navegador (o cliente a cliente).
graph TD
subgraph "Figma (Centralizado)"
A1[Usuario A] -->|Cambios| S[Servidor Figma]
S -->|Sync| B1[Usuario B]
S -->|Sync| C1[Usuario C]
end
subgraph "OpenPencil (P2P)"
A2[Usuario A] -->|WebRTC| B2[Usuario B]
A2 -->|WebRTC| C2[Usuario C]
B2 -->|WebRTC| C2
end
Las tecnologías detrás de la colaboración
WebRTC: el transporte
WebRTC (Web Real-Time Communication) es una API nativa del navegador que permite comunicación P2P directa. Fue diseñada originalmente para videollamadas, pero es perfectamente apta para sincronización de datos en tiempo real.
El establecimiento de la conexión WebRTC requiere un proceso de señalización (intercambio de metadatos para encontrarse). OpenPencil usa Trystero como librería que abstrae este proceso usando canales de señalización temporales (puede usar Nostr, Firebase, MQTT o un servidor propio mínimo).
Trystero: el broker de señalización
Trystero es una librería de señalización P2P que permite crear “rooms” identificadas por un string. Cuando dos personas entran al mismo room ID, Trystero las conecta via WebRTC sin que ningún dato de diseño pase por el servidor de señalización.
sequenceDiagram
participant A as Usuario A
participant T as Trystero (señalización)
participant B as Usuario B
A->>T: "Quiero unirme al room 'proyecto-xyz'"
B->>T: "Quiero unirme al room 'proyecto-xyz'"
T->>A: ICE candidates de B
T->>B: ICE candidates de A
A->>B: WebRTC handshake directo
B->>A: WebRTC handshake directo
Note over A,B: Conexión P2P establecida
T-->>A: (Ya no interviene)
T-->>B: (Ya no interviene)
A->>B: Cambios CRDT directos
B->>A: Cambios CRDT directos
Yjs: el CRDT
Yjs es una implementación de CRDT (Conflict-free Replicated Data Type) en JavaScript. Es la tecnología clave que permite la edición colaborativa sin conflictos.
Un CRDT es una estructura de datos que puede ser modificada por múltiples participantes de forma concurrente, y siempre converge al mismo estado final sin importar el orden en que lleguen los cambios.
sequenceDiagram
participant A as Usuario A (offline)
participant B as Usuario B (online)
participant C as Usuario C (online)
Note over A: Desconectado durante 5 minutos
B->>C: Mueve el frame a x:100
C->>B: Cambia el color a #2563EB
Note over A: Se reconecta
A->>B: Cambios pendientes: renombra el frame
B->>A: Estado actual (merge automático)
Note over A,B,C: Todos convergen al mismo estado
Tipos de datos Yjs en OpenPencil
| Estructura del documento | Tipo Yjs |
|---|---|
| Árbol de nodos | Y.Map anidado |
| Lista de children | Y.Array |
| Propiedades de texto | Y.Text |
| Posición de cursores | Y.Awareness |
| Historial de operaciones | Y.UndoManager |
La elección de tipos Yjs determina cómo se resuelven los conflictos. Por ejemplo:
- Si A y B mueven el mismo nodo al mismo tiempo, la última operación gana (basado en timestamp lógico)
- Si A y B editan el mismo texto al mismo tiempo, los cambios se fusionan carácter por carácter
- Si A elimina un nodo y B lo modifica al mismo tiempo, la eliminación gana (tombstone)
Crear una sesión colaborativa
Desde la interfaz gráfica
- Abre el documento que quieres compartir
- Click en el ícono de “Colaborar” en la barra superior (ícono de personas)
- Click en “Crear room”
- OpenPencil genera un link único:
https://app.openpencil.dev/room/abc123xyz - Comparte el link con tu equipo
El link es temporal por defecto (expira cuando todos los participantes se desconectan). Para un link persistente que sobreviva las sesiones:
Opción A: Hostea tu propio servidor de señalización Trystero y configura OpenPencil para usarlo.
Opción B: Usa la versión Docker con una configuración de room persistente.
Desde el CLI
# Iniciar una sesión colaborativa desde el CLI
op collab start mi-proyecto.op
# Output:
# 🔗 Room creado: https://app.openpencil.dev/room/k7n2m9p
# 🔗 O con el CLI: op collab join k7n2m9p
# ⏳ Esperando participantes... (Ctrl+C para terminar)
# Unirse a una sesión desde el CLI
op collab join k7n2m9p
# Listar participantes actuales
op collab participants k7n2m9p
Con Docker
Para equipos que quieren una sesión persistente con server propio:
# docker-compose.collab.yml
services:
openpencil-relay:
image: ghcr.io/zseven-w/openpencil-relay:latest
ports:
- "8080:8080"
environment:
- RELAY_AUTH_TOKEN=${RELAY_TOKEN}
openpencil:
image: ghcr.io/zseven-w/openpencil:latest
ports:
- "3000:3000"
volumes:
- ./designs:/app/designs
environment:
- RELAY_URL=ws://openpencil-relay:8080
- RELAY_TOKEN=${RELAY_TOKEN}
Cursores en tiempo real
Una vez en un room colaborativo, los cursores de todos los participantes son visibles en el canvas en tiempo real. Cada participante tiene:
- Un color único asignado automáticamente (consistente entre sesiones para el mismo usuario)
- Una etiqueta con el nombre flotando junto al cursor
- Un indicador de selección: los nodos seleccionados por cada participante se muestran con el color de ese participante
Implementación técnica de los cursores
Los cursores usan Y.Awareness de Yjs, que es un canal de datos ephemeral (no persisten en el documento). Cuando un usuario mueve el cursor, se emite un update de awareness que se propaga a todos los participantes en milisegundos:
// Internamente, OpenPencil hace algo así:
const awareness = new Awareness(ydoc)
// Cuando el cursor se mueve
canvas.on('mousemove', (event) => {
awareness.setLocalStateField('cursor', {
x: event.canvasX,
y: event.canvasY,
user: {
name: currentUser.name,
color: currentUser.color
}
})
})
// Para renderizar cursores remotos
awareness.on('change', () => {
const states = awareness.getStates()
renderRemoteCursors(states)
})
Viewport following
El “viewport following” permite a un participante “seguir” la vista de otro. Útil para demostraciones, revisiones y handoffs de diseño.
Cómo activarlo
- En el panel de colaboración (panel derecho cuando hay otros participantes)
- Click en el avatar de otro participante
- Selecciona “Seguir” (Follow)
- Tu vista se sincroniza automáticamente con la del participante seguido
Cuando estás siguiendo a alguien:
- Tu canvas hace scroll y zoom automáticamente para mostrar lo que esa persona está viendo
- Un banner en la parte superior indica “Siguiendo a [nombre]”
- Cualquier scroll o zoom tuyo interrumpe el following
Presentaciones asistidas por IA
Puedes combinar el following con el agente de IA para hacer demos dinámicas:
# Primero, pide a todos que te sigan
# Luego, en el chat:
/present: "Muéstrame cada sección del diseño de la landing page,
haciendo zoom en los elementos clave y explicando las
decisiones de diseño"
El agente navega automáticamente por el canvas haciendo zoom en las secciones importantes, mientras el resto del equipo sigue tu viewport.
Persistencia y sincronización offline
Un aspecto crucial de la arquitectura P2P de OpenPencil es cómo maneja la desconexión y reconexión.
Modelo de persistencia
Cada participante mantiene una copia local completa del documento Yjs. Cuando te desconectas:
- Sigues pudiendo editar en local
- Los cambios se almacenan en el
Y.UndoManager - Al reconectarte, los cambios pendientes se aplican automáticamente
Si el documento nunca tuvo un servidor central, ¿cómo se sincroniza cuando un participante se une después?
Respuesta: El documento .op actúa como el “estado canónico”. Cuando alguien crea la sesión, carga el documento actual como el estado inicial de Yjs. Cuando alguien se une, recibe ese estado y lo fusiona con cualquier cambio local que pueda tener.
Manejo de conflictos comunes
| Situación | Resolución |
|---|---|
| A y B mueven el mismo nodo | Gana la última operación (por reloj lógico) |
| A edita texto, B edita texto en la misma posición | Merge character-level (ambos cambios se preservan) |
| A elimina un nodo, B lo modifica | La eliminación gana (tombstone marking) |
| A agrega un child, B agrega otro child al mismo padre | Ambos se agregan (orden determinístico por ID) |
| A y B cambian el mismo color | Gana la última operación |
Guardar el estado colaborativo
Mientras hay una sesión activa, los cambios se aplican al documento en memoria. Para persistir:
# Los participantes pueden guardar el estado actual
# En la UI: Cmd/Ctrl+S guarda tu copia local
# Desde el CLI
op collab save k7n2m9p --output mi-proyecto-colaborativo.op
# Configurar autosave en la sesión
op collab start mi-proyecto.op --autosave 30 # Guarda cada 30 segundos
Permisos y control de acceso
OpenPencil implementa un sistema de permisos básico para las sesiones colaborativas:
Roles
| Rol | Puede editar | Puede comentar | Puede ver |
|---|---|---|---|
| Owner | Sí | Sí | Sí |
| Editor | Sí | Sí | Sí |
| Commenter | No | Sí | Sí |
| Viewer | No | No | Sí |
# Crear un room con permisos específicos
op collab start mi-proyecto.op \
--default-role viewer \
--allow-comment \
--password "mi-contraseña-segura"
# Crear link de editor (no necesita contraseña)
op collab invite k7n2m9p \
--role editor \
--expires 24h # Link válido por 24 horas
Control de acceso con autenticación
Para sesiones empresariales con control de acceso completo:
// ~/.config/openpencil/collab.json
{
"auth": {
"provider": "github", // o "google", "custom-oauth"
"clientId": "...",
"allowedDomains": ["miempresa.com"]
},
"rooms": {
"defaultRole": "viewer",
"requireAuth": true
}
}
Comentarios y anotaciones
Además de la edición colaborativa, OpenPencil soporta comentarios en el canvas:
Agregar un comentario
- Pulsa
Cpara activar la herramienta de comentario - Click en cualquier punto del canvas para agregar un pin de comentario
- Escribe el comentario en el popover
- Opcionalmente, menciona a alguien con
@nombre - Presiona Enter para guardar
Los comentarios son persistentes (se guardan en el archivo .op) y se sincronizan en tiempo real entre todos los participantes.
Resolución de comentarios
Los comentarios tienen estados:
- Abierto: Requiere atención
- Resuelto: Marcado como completado (se puede reabrir)
- Archivado: Oculto de la vista principal
# Ver todos los comentarios de un documento
op comments list mi-proyecto.op
# Exportar comentarios como JSON
op comments export mi-proyecto.op --format json
# Marcar todos como resueltos (útil antes de handoff)
op comments resolve-all mi-proyecto.op
Comparación con el modelo de Figma
Entender las diferencias arquitectónicas entre OpenPencil y Figma ayuda a elegir cuándo usar cada uno.
graph LR
subgraph Figma
FA[Client A] -->|HTTPS| FS[Figma Servers]
FB[Client B] -->|HTTPS| FS
FC[Client C] -->|HTTPS| FS
FS -->|WebSocket| FA
FS -->|WebSocket| FB
FS -->|WebSocket| FC
end
subgraph OpenPencil
OA[Client A] -->|WebRTC| OB[Client B]
OA -->|WebRTC| OC[Client C]
OB -->|WebRTC| OC
end
| Aspecto | Figma | OpenPencil |
|---|---|---|
| Infraestructura | Servidores centralizados | P2P (sin servidor de datos) |
| Privacidad | Datos en servidores de Figma | Datos solo en los clientes |
| Offline | Solo vista, sin edición | Edición completa offline |
| Latencia | ~50-200ms (depende de la ubicación del servidor) | ~1-50ms (conexión directa) |
| Escala | Ilimitada (con plan pagado) | Limitada por conexiones P2P (~20 usuarios cómodos) |
| Persistencia | Automática en la nube | Manual (guardar el archivo) |
| Historia de cambios | Completa en el servidor | Solo con Git |
Cuándo OpenPencil P2P es mejor
- Equipos pequeños (2-10 personas): La latencia P2P supera a la centralizada
- Datos sensibles: Los diseños nunca salen de los equipos de los participantes
- Sin conexión a internet confiable: Cada participante tiene la copia completa
- Integración con Git: El diseño como archivo versionable es el flujo natural
Cuándo Figma puede ser mejor
- Equipos grandes (50+ personas): La arquitectura centralizada escala mejor
- Clientes sin acceso técnico: Un link de Figma es más accesible que un room de WebRTC
- Organizaciones con compliance estricto: Los controles de acceso empresariales de Figma son más maduros
Flujo de trabajo colaborativo recomendado
Para equipos que adoptan OpenPencil como herramienta principal de colaboración:
flowchart TD
A[Diseñador crea/modifica el diseño] --> B[Commit del .op a Git]
B --> C{¿Revisión en vivo?}
C -->|Sí| D[op collab start diseño.op]
D --> E[Comparte el link al equipo]
E --> F[Sesión colaborativa P2P]
F --> G[Feedback y cambios]
G --> H[Diseñador guarda estado final]
C -->|No| I[PR en GitHub con el .op actualizado]
H --> I
I --> J[Revisión del diff del .op]
J --> K[Merge a main]
K --> L[CI exporta componentes actualizados]
Resumen
En este capítulo has aprendido:
- La arquitectura P2P de OpenPencil con WebRTC, Trystero y Yjs
- Cómo funciona un CRDT y por qué garantiza consistencia sin servidor
- Crear y gestionar rooms colaborativos desde la UI y el CLI
- Cursores en tiempo real y viewport following
- Manejo de conflictos y persistencia offline
- Sistema de permisos y comentarios
- Comparación honesta con el modelo centralizado de Figma
En el próximo capítulo aprenderemos el sistema de componentes de OpenPencil.