> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doconda.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Edición en tiempo real

> Varias personas editando a la vez el mismo Word, Excel, PowerPoint o PDF: en el servidor de Doconda o en el tuyo.

Con `collab`, todos los que abren el mismo documento en el editor ven los cambios de los demás mientras escriben, con
su nombre y su color en el cursor. Funciona en Word, Excel, PowerPoint y PDF (en PDF se comparten las anotaciones). La
barra de estado muestra si el editor está conectado y cuántas personas más están editando.

Tú eliges quién lleva los cambios:

| | Qué le das al editor | Quién pone el servidor |
| - | - | - |
| **Servidor de Doconda** | `collab={{ token, user }}` | Doconda, incluido en la licencia |
| **Tu servidor** | `collab={{ url, user }}` | Tú: un servidor WebSocket que reenvía mensajes |
| **Tu transporte** | `collab={{ provider, user }}` | Tú: cualquier canal (WebRTC, tu servicio en tiempo real…) |

Pasa solo uno de `token`, `url` o `provider`. `user` es `{ id, name }`: `name` se ve en la pantalla de los
demás junto al cursor, y firma los comentarios y los cambios registrados si no pasas `author`. Cada pantalla elige el
color de los demás, como hace Word: el mismo mientras siguen conectados y nunca el azul de tu propia selección. En Word
se ve su cursor y su selección; en Excel, las celdas que tienen seleccionadas; en PowerPoint, las formas que tienen
seleccionadas.

El control de cambios funciona como el de Word: cada uno elige **Para todos** (el documento registra todos los cambios) o
**Solo los míos** (solo se registran sus cambios; los demás siguen editando sin registro). Los cambios y los comentarios
se ven también como tarjetas en el margen cuando la página deja sitio, con aceptar y rechazar en cada cambio.

Se puede hacer de todo mientras se edita en compañía: añadir, borrar o mover diapositivas y hojas, imágenes, gráficos,
tablas, tablas dinámicas, comentarios, notas, temas, diseños y propiedades del documento viajan a los demás, y cada uno
deshace solo lo suyo. Lo único que espera: en un Word que aún no tiene ninguna lista, no se pueden empezar listas
mientras se edita en compañía. Dos límites conocidos cuando dos personas hacen lo mismo en el mismo instante: dos hojas
nuevas con el mismo nombre (las dos «Hoja3») quedan duplicadas, y si las dos añaden el primer gráfico o imagen de la
misma hoja, se queda uno.

## En el servidor de Doconda

Tu backend pide un token para el documento con tu clave de API y el editor se conecta con él. La **sala** (`room`) es
tu identificador del documento: todos los que tienen un token de la misma sala editan juntos.

```ts Tu backend theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { Doconda } from "@doconda/sdk"

const doconda = new Doconda() // DOCONDA_API_KEY

app.post("/api/documents/:id/collab-token", async (req, res) => {
  // comprueba que este usuario puede abrir este documento y luego:
  const { token } = await doconda.editor.collabToken(`doc-${req.params.id}`)
  res.json({ token })
})
```

```python Tu backend (Python) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from doconda import Doconda

token = Doconda().editor.collab_token(f"doc-{document_id}")["token"]
```

```tsx Tu página theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
<DocondaEditor
  src={`/files/${id}.docx`}
  licenseKey="ek_eu_…"
  collab={{
    token: () => fetch(`/api/documents/${id}/collab-token`, { method: "POST" }).then((r) => r.json()).then((j) => j.token),
    user: { id: me.id, name: me.name },
  }}
/>
```

* El token sirve **10 minutos** para conectarse, solo en esa sala y solo desde los dominios de tu licencia (y
  `localhost` mientras desarrollas). Una conexión ya abierta sigue abierta.
* Pasa `token` como función: el editor la vuelve a llamar cada vez que tiene que reconectar. Un texto fijo también
  vale, pero una reconexión pasados 10 minutos se rechaza.
* Sin el SDK es `POST /editor/collab/token` con `{ "room": "…" }` (en la referencia de la API, **Editor**).
* Hasta 50 personas por sala. Doconda no guarda nada: los cambios solo viajan entre los que están conectados.

## En tu servidor

El editor habla un protocolo muy pequeño sobre WebSocket, así que el servidor solo tiene que **reenviar cada mensaje
al resto de personas de la misma sala** (no al que lo envía). Este es un servidor completo en Node con
[`ws`](https://github.com/websockets/ws):

```ts server.ts theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { WebSocketServer } from "ws"

const rooms = new Map() // sala → conjunto de sockets

new WebSocketServer({ port: 8080 }).on("connection", (socket, req) => {
  // autentica aquí: quién es este usuario y si puede abrir este documento
  const room = new URL(req.url, "http://x").searchParams.get("room")
  const peers = rooms.get(room) ?? rooms.set(room, new Set()).get(room)
  peers.add(socket)
  let peer // el último id de presencia que ha enviado este socket
  socket.on("message", (data, binary) => {
    if (!binary) return
    if (data[0] === 1) peer = JSON.parse(data.subarray(1)).peer
    for (const other of peers) if (other !== socket) other.send(data)
  })
  socket.on("close", () => {
    peers.delete(socket)
    if (!peers.size) rooms.delete(room)
    const left = Buffer.concat([Buffer.from([1]), Buffer.from(JSON.stringify({ peer, state: null }))])
    if (peer) for (const other of peers) other.send(left)
  })
})
```

```tsx Tu página theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
<DocondaEditor src={url} collab={{ url: `wss://collab.acme.com/?room=doc-${id}`, user: { name: me.name } }} />
```

Cada mensaje es binario. Su primer byte dice qué es:

| Byte | Contenido | Qué hace el servidor |
| - | - | - |
| `0` | Un cambio del documento (una actualización de Yjs) | Reenviarlo |
| `1` | Presencia, JSON `{ peer, state }` (nombre, cursor) | Reenviarlo |
| `2` | Saludo, vacío: alguien acaba de conectarse y pide a los demás su estado | Reenviarlo |

Cuando se cierra una conexión, envía a los demás `1` + `{ "peer": <su último peer>, "state": null }` para que
desaparezca su cursor (el ejemplo de arriba lo hace). Autentica como prefieras: una cookie, o un token en la URL que
pone tu página. Para rechazar una conexión del todo, ciérrala con un código de **4000 a 4999**: el editor no lo vuelve
a intentar. Cualquier otro cierre (un reinicio, un corte de red) hace que reconecte solo, esperando un poco más cada
vez.

## Con tu propio transporte

`provider` es cualquier objeto con `send(update)`, `onUpdate(cb)`, `onConnect(cb)`, `setAwareness(state)` y
`onAwareness(cb)`. El editor exporta `WebSocketProvider`, el que usa con `url` y `token`, por si quieres partir de él:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { WebSocketProvider } from "@doconda/editor"

const provider = new WebSocketProvider(() => new WebSocket(miUrl()), crypto.randomUUID())
```

## Guardar

La sala solo existe mientras hay alguien conectado: cuando se va el último, el documento es lo que cada uno haya
guardado. Sigue guardando como siempre con `onSave` (o `ref.save()`): todos los de la sala tienen el mismo contenido,
así que da igual quién guarde. El siguiente que abra el documento abre el archivo guardado. Con `collab` activo, la
copia de recuperación del navegador (`autosave`) se desactiva, para que una copia vieja no pise el trabajo de los
demás.

## Problemas de conexión

* El editor abre el documento cuando ha entrado en la sala y ha recibido lo que tienen los demás (así nunca pisa su
  trabajo con un archivo más antiguo); hasta entonces muestra **Conectando…**.
* La barra de estado muestra **Conectando…** (se perdió la conexión y está reintentando), el número de personas
  editando o **Sin conexión**.
* Si el editor no puede entrar (un token caducado, un dominio que no está en la licencia, una sala llena), abre el
  documento sin edición compartida y `onError` recibe `editor_collab_closed`.
* En el servidor de Doconda los códigos de cierre son `4401` (token no válido o caducado), `4403` (sin licencia para
  este dominio), `4429` (sala llena) y `4413` (un mensaje de más de 64 MB).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.