Colaboración en tiempo real

Por: Artiko
openpencilcolaboraciónwebrtccrdtyjsp2ptiempo-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:

  1. Dependencia del servidor: Si el servidor cae, la colaboración se detiene
  2. Privacidad de los datos: Todo el contenido pasa por servidores de terceros
  3. 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 documentoTipo Yjs
Árbol de nodosY.Map anidado
Lista de childrenY.Array
Propiedades de textoY.Text
Posición de cursoresY.Awareness
Historial de operacionesY.UndoManager

La elección de tipos Yjs determina cómo se resuelven los conflictos. Por ejemplo:

Crear una sesión colaborativa

Desde la interfaz gráfica

  1. Abre el documento que quieres compartir
  2. Click en el ícono de “Colaborar” en la barra superior (ícono de personas)
  3. Click en “Crear room”
  4. OpenPencil genera un link único: https://app.openpencil.dev/room/abc123xyz
  5. 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:

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

  1. En el panel de colaboración (panel derecho cuando hay otros participantes)
  2. Click en el avatar de otro participante
  3. Selecciona “Seguir” (Follow)
  4. Tu vista se sincroniza automáticamente con la del participante seguido

Cuando estás siguiendo a alguien:

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:

  1. Sigues pudiendo editar en local
  2. Los cambios se almacenan en el Y.UndoManager
  3. 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ónResolución
A y B mueven el mismo nodoGana la última operación (por reloj lógico)
A edita texto, B edita texto en la misma posiciónMerge character-level (ambos cambios se preservan)
A elimina un nodo, B lo modificaLa eliminación gana (tombstone marking)
A agrega un child, B agrega otro child al mismo padreAmbos se agregan (orden determinístico por ID)
A y B cambian el mismo colorGana 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

RolPuede editarPuede comentarPuede ver
Owner
Editor
CommenterNo
ViewerNoNo
# 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

  1. Pulsa C para activar la herramienta de comentario
  2. Click en cualquier punto del canvas para agregar un pin de comentario
  3. Escribe el comentario en el popover
  4. Opcionalmente, menciona a alguien con @nombre
  5. 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:

# 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
AspectoFigmaOpenPencil
InfraestructuraServidores centralizadosP2P (sin servidor de datos)
PrivacidadDatos en servidores de FigmaDatos solo en los clientes
OfflineSolo vista, sin ediciónEdición completa offline
Latencia~50-200ms (depende de la ubicación del servidor)~1-50ms (conexión directa)
EscalaIlimitada (con plan pagado)Limitada por conexiones P2P (~20 usuarios cómodos)
PersistenciaAutomática en la nubeManual (guardar el archivo)
Historia de cambiosCompleta en el servidorSolo con Git

Cuándo OpenPencil P2P es mejor

Cuándo Figma puede ser mejor

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:

En el próximo capítulo aprenderemos el sistema de componentes de OpenPencil.