Volver a la wiki

Cuaderno — Multiusuario y Sharing Colaborativo

Cuaderno — Multiusuario y Sharing Colaborativo

Introducida en el commit ab1c02f (2026-05-20), esta feature convierte el Cuaderno de un módulo mono-usuario a un módulo colaborativo con control de acceso por nota, detección de edición concurrente y adjuntos de imagen.

Modelo de acceso (sharing)

Cada notebook_note tiene el campo shared_with TEXT NOT NULL DEFAULT '' con tres semánticas:

Valor de shared_withSignificado
'' (cadena vacía)Privada — solo el owner_id puede leer y editar
'ALL'Todo el equipo — cualquier miembro del staff con sesión activa
'a@x.com,b@y.com'CSV de emails — solo los destinatarios explícitos

Seguridad de la búsqueda LIKE: el filtro shared_with LIKE '%email%' es seguro con emails @esfericlabs.com porque no hay colisiones de substrings en el staff actual. Si se incorporara staff externo, revisar.

Reglas de autorización

Cambios de esquema (Migración 0032)

Archivo: migrations/0032_notebook_sharing.sql

ALTER TABLE notebook_notes ADD COLUMN shared_with TEXT NOT NULL DEFAULT '';
ALTER TABLE notebook_notes ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
CREATE INDEX IF NOT EXISTS idx_notebook_shared_with ON notebook_notes(shared_with);

El campo version se incrementa con version = version + 1 en cada PUT, lo que permite al frontend detectar saves concurrentes mediante polling ligero.

Endpoints afectados / nuevos

GET /api/notebook (modificado)

GET /api/notebook/[id] (refactorizado)

Movido de functions/api/notebook/[id].ts a functions/api/notebook/[id]/index.ts.
loadOwned → loadAccessible: usa hasAccess() en lugar de filtrar solo por owner_id.

PUT /api/notebook/[id] (modificado)

GET /api/notebook/[id]/version (nuevo)

Polling endpoint ligero. Devuelve solo { version, updated_at }. Usado por el editor cada 30 s en notas compartidas para mostrar el banner de edición concurrente.

POST /api/notebook/[id]/images (nuevo)

Upload multipart a R2. Restricciones: 5 MB máx, tipos permitidos image/png | jpeg | webp | gif. Guarda fila en notebook_attachments y devuelve { id, url, filename, content_type }.

GET /api/notebook-images/[id] (nuevo)

Stream del objeto R2. Verifica acceso a la nota padre antes de servir. Headers: Cache-Control: private, max-age=3600.

Cambios en frontend

Toolbar — 7 nuevas funciones

BotónAcción
↶ Undoundo de @codemirror/commands
↷ Redoredo de @codemirror/commands
AaToggle spellcheck/autocorrect/autocapitalize vía Compartment (persistido en localStorage)
⚠Inserta !!texto!! → renderizado como <mark class="urgent"> (bg rojo translúcido + border izq rojo)
🖼File input → POST /api/notebook/[id]/images → inserta ![alt](/api/notebook-images/N)
😊Abre EmojiPicker custom (~100 emojis, 6 categorías, búsqueda, sin deps externas)
CompartirShareDropdown (Privada / Todo el equipo / multi-select). Solo visible al owner.

En notas compartidas el editor hace polling cada 30 s a /api/notebook/[id]/version. Si version remota > version local, muestra banner amarillo:
“X editó esta nota hace Nm. Refrescar / Ignorar”

NoteRow en sidebar

NoteEditor meta header

Muestra "Compartida por X" (notas ajenas) o el label "Compartida · Edu, Dani" (propias compartidas).

Dependencias técnicas

Limitaciones conocidas

Véase también

Subir