# STARGATE — Sistema Operativo para Campañas | Baja California 2027

Deck ejecutivo interactivo y **estudio de autoría de presentaciones** que funciona
íntegramente en el navegador, sin servidor, sin build y sin dependencias de backend.

Escenario nativo **9:16** (conmutable a 16:9), pizarra táctica en tiempo real,
Oráculo Estratégico IA con narración en español de México, **maquetador libre tipo
Figma**, biblioteca multi‑presentación, zoom por áreas, compartición por enlace y
WhatsApp con modo invitado, **paleta de comandos universal**, **menú único con todas
las herramientas**, **grabar / deshacer global**, **asistente de nueva presentación**,
**vista del presentador en ventana aparte con dibujo sincronizado**, **exportación
con rango de páginas y envío a Google Slides**, **transiciones animadas entre
láminas** y, como herramienta hermana e independiente, **«Presentar un proyecto»**:
una **pizarra infinita al estilo Excalidraw** para construir, exponer y ejemplificar
proyectos, con `inicio.html` como portada para elegir entre las dos herramientas.

---

## 1. Objetivos del proyecto

1. Presentar el sistema STARGATE con calidad de producción audiovisual.
2. Permitir que el propio equipo **edite, maquete y cree presentaciones nuevas** sin
   salir del navegador ni instalar nada.
3. **Compartir** una presentación con terceros mediante un enlace o WhatsApp, de
   modo que el invitado pueda navegarla, ampliar áreas concretas y —si se autoriza—
   manipularla.
4. No enviar nunca contenido a un servidor: todo vive en el dispositivo.
5. Ofrecer, como **herramienta separada e igual de completa**, una pizarra infinita
   para diagramar, explicar y presentar proyectos por pasos (frames), sin mezclarla
   con el editor de diapositivas.

---

## 2. Arquitectura

Sitio estático puro: HTML + CSS + JavaScript vanilla. Cada módulo es una IIFE que
publica una API en `window.Stargate*`. No hay framework, empaquetador ni backend.

**Dos herramientas, dos documentos HTML, un vestíbulo:**

| Página | Herramienta | Publica |
|---|---|---|
| `inicio.html` | **Portada** — elige entre las dos herramientas, muestra recientes | (script inline, sin módulo) |
| `index.html` | **Crear presentaciones** — el deck ejecutivo y su Studio | `js/*.js` de la tabla siguiente |
| `proyecto.html` | **Presentar un proyecto** — pizarra infinita tipo Excalidraw | `window.WB`, `window.WBUI`, `window.StargateWBCloud` |

Cada herramienta tiene su propio conjunto de CSS y JS, no comparte estado ni DOM,
y se navega de una a otra con un botón visible (nunca mezclando ambas interfaces
en una sola pantalla). Ver §3.19 para el detalle de la pizarra.

`proyecto.html` carga, en orden: `js/whiteboard.js` → `js/whiteboard-ui.js` →
`js/wb-cloud.js` (enlaces cortos por nube, opcional: si el módulo no puede
cargarse por lo que sea, la pizarra sigue funcionando normalmente con el
enlace largo `#p=`).

### Orden de carga (obligatorio)

| # | Archivo | Publica | Responsabilidad |
|---|---|---|---|
| 1 | `js/slides.js` | `STARGATE_SLIDES` | Contenido de fábrica de las láminas |
| 2 | `js/content.js` | `StargateContent` | Persistencia, biblioteca de decks, modo invitado |
| 3 | `js/board.js` | `StargateBoard` | Pizarra, tinta, láser |
| 4 | `js/main.js` | `StargateDeck` | Navegación, temas, encaje, índice, presentador |
| 5 | `js/zoom.js` | `StargateZoom` | Zoom, paneo y lupa de área |
| 6 | `js/share.js` | `StargateShare` | Enlace comprimido, WhatsApp, QR, invitado |
| 7 | `js/export.js` | `StargateExport` | PDF, PPTX, PNG, guion |
| 8 | `js/editor.js` | `StargateStudio` | Studio, pestañas, biblioteca de presentaciones |
| 9 | `js/layout.js` | `StargateLayout` | **Maquetador libre**, inspector, grupos, portapapeles |
| 10 | `js/history.js` | `StargateHistory` | Deshacer / rehacer de edición |
| 11 | `js/import.js` | `StargateImport` | Importación de PPTX / PDF / texto |
| 7b | `js/presenter.js` | `StargatePresent` | **Botón Presentar, vista del presentador (ventana), exportación nueva** |
| 12 | `js/cloud.js` | `StargateCloud` | **Enlaces cortos y comentarios de revisión** |
| 13 | `js/viewer.js` | `StargateViewer` | **Visor de invitado**: solo lectura, zoom, pantalla completa, orientación automática |
| 14 | `js/save.js` | `StargateSave` | **Grabar, deshacer/rehacer global, puntos de restauración** |
| 15 | `js/components.js` | `StargateComponents` | Arsenal de componentes reutilizables |
| 16 | `js/offline.js` | `StargateOffline` | Panel del *service worker* (`sw.js`) |
| 17 | `js/creator.js` | `StargateCreator` | **Asistente «Crear nueva presentación»** |
| 18 | `js/command.js` | `StargateCommand` | Paleta de comandos universal |
| 19 | `js/menu.js` | `StargateMenu` | **Menú principal con todas las herramientas** (F1) |

`share.js` debe cargarse **después** de `main.js` porque necesita `StargateDeck`
para abrir un deck recibido por enlace. `presenter.js` va tras `export.js` porque
recablea su panel. `cloud.js` va después de `share.js` porque reutiliza su
empaquetador (`pack`) y su lector (`openFrag`). `viewer.js` va tras ambos porque
recibe el mando cuando abren un enlace. `save.js` tras `content.js`;
`components.js` tras `layout.js`; `creator.js` tras `content.js` y `save.js`.
`menu.js` va **el último** porque su catálogo consulta todos los módulos.

> Este orden de carga aplica **solo a `index.html`** (Crear presentaciones).
> `proyecto.html` (Presentar un proyecto) es un documento independiente que carga
> únicamente `js/whiteboard.js` y, después, `js/whiteboard-ui.js` — ver §3.19.

### Hojas de estilo

`css/style.css` (tokens y escenario) → `css/premium.css` (láminas) →
`css/editor.css` (Studio) → `css/layout.css` (maquetador) →
`css/share.css` (zoom, biblioteca, compartir) → `css/command.css` (paleta) →
`css/cloud.css` (nube y comentarios) → `css/menu.css` (menú y visor de invitado) →
`css/save.css` → `css/creator.css` (asistente) → `css/presenter.css` (barra de
marcaje, botón Presentar, exportación, Google Slides, transiciones, navegación
cruzada hacia «Presentar un proyecto»).

`proyecto.html` usa su propia hoja **`css/whiteboard.css`**, con variables de tema
propias (`--wb-*`) e independiente del sistema `--u` del deck (ver §3.19).

### Sistema de unidades

Todo el tipografiado y la geometría se derivan de variables CSS:
`--u` (1 % de la altura del deck) × `--fit`, con `--t` como escala de texto y
`--edit-scale` para el modo edición. **Dentro de `#deck` no se usan `color-mix()`
ni `backdrop-filter`**, para que la exportación con `html2canvas` sea fiel al píxel.

---

## 3. Funcionalidades completadas

### 3.1 Presentación

- Escenario nativo **9:16** conmutable a **16:9** (`R`).
- Tema **noche / día** (`D`), escala tipográfica global, modo limpio (`H`).
- Navegación por botones, puntos, teclado, rueda del ratón y *swipe* táctil.
- **Índice buscador** de láminas (`O`), **autoavance** (`P`), **modo foco /
  reflector** (`S`), pantalla completa (`F`).
- Contadores animados, animación escalonada de entrada y partículas de fondo que
  respetan `prefers-reduced-motion`.

#### Botón **Presentar** (`N`) con desplegable

| Opción | Qué hace |
|---|---|
| **Desde el inicio** | Lámina 1, interfaz oculta y pantalla completa |
| **Desde la diapositiva actual** | Igual, desde donde estás |
| **Vista del presentador** | Abre una **ventana aparte** (para la segunda pantalla) |
| **Panel compacto aquí** | El antiguo panel con cronómetro y guion en la misma pantalla |

#### Vista del presentador (ventana aparte)

Reloj, **cronómetro** con pausar / reanudar / reiniciar, la lámina actual a gran
tamaño, **vista previa de la siguiente**, **notas del orador editables** (se graban
en la lámina y marcan un punto de deshacer), tamaño de letra de las notas,
Anterior / Siguiente y atajos (`←` `→` `D` `Esc`).

**Dibujar en la página**: al activarlo, lo que se traza en la ventana del presentador
aparece **en la pantalla principal en tiempo real** (trazo provisional mientras se
arrastra y trazo definitivo al soltar). «Borrar dibujos en esta página» limpia la
lámina en ambas pantallas. La ventana no ejecuta scripts propios: la principal le
inyecta el DOM y escucha sus eventos, por lo que si el navegador bloquea las ventanas
emergentes se abre el panel compacto en su lugar.

### 3.2 Pizarra táctica (barra estilo pizarra colaborativa)

Barra con **badge del atajo** en cada herramienta: **candado** (fijar herramienta),
**mano** (mover la vista), puntero, rectángulo, **rombo**, elipse, flecha, línea,
pluma, marcador, subrayado, texto, borrador por objeto y **puntero láser con
estela**; colores y grosores; deshacer / rehacer / borrar.

- **Comportamiento tipo Excalidraw**: tras dibujar una figura (rectángulo, rombo,
  elipse, flecha, línea, subrayado) la herramienta **vuelve al puntero**; con el
  **candado** (`B` o doble clic en la figura) se mantiene. Pluma y marcador siempre
  se mantienen.
- **Mano** (`M`): arrastra para mover la vista cuando hay zoom; en modo mano no se
  dibuja y funcionan rueda y *swipe*.
- En **formato horizontal** en pantallas anchas la barra pasa a ser una **píldora
  flotante centrada arriba**; en vertical es el riel izquierdo; en teléfono es la
  barra inferior (sin badges).
- Coordenadas normalizadas (0–1), soporte `devicePixelRatio`, tinta por lámina,
  deshacer de trazo y borrado (`C`). API para tinta remota: `addStroke()`,
  `preview()`, `paintStroke()`, `strokesOf()`.

### 3.3 Oráculo Estratégico IA

Síntesis ejecutiva y ventaja competitiva por lámina, con narración
`SpeechSynthesisUtterance` priorizando `es-MX → es-US → es-419 → es-*`.

### 3.4 Studio de edición

Puerta con clave (`Ctrl+Shift+E`). Pestañas: **Contenido**, **Presentaciones**,
**Diseño**, **Imágenes**, **Maquetar**, **Láminas**, **Datos**.

- Edición de texto en vivo sobre la lámina con barra de formato.
- Plantillas visuales, tipografías, acento de color, imagen de fondo con atenuación.
- Crear, duplicar, restaurar y reordenar láminas.
- Copia de seguridad JSON y restauración.

> **Nota de seguridad:** la clave del Studio es una comprobación **en el
> navegador**, legible en el código fuente. No es seguridad real y no debe
> tratarse como tal: un sitio estático no puede proteger de verdad un área de
> administración. Solo evita ediciones accidentales durante una proyección.

### 3.5 Maquetador libre (el núcleo reparado)

El motor de maquetación estaba inoperativo por un defecto de **granularidad**:
`liberate()` medía únicamente los *hijos directos* de `.slide-inner`, de modo que
arrastrar un título movía el bloque contenedor completo (2 «átomos» por lámina).

Ahora `atoms()` **desciende recursivamente** por los contenedores de maquetación
(`header`, `section`, `div`, retículas, filas de KPI…) hasta los elementos atómicos
reales. Verificado: **9 átomos** en la lámina de prueba en lugar de 2, y el título
se mueve solo.

Capacidades:

- Mover, redimensionar con 8 manijas, escalar proporcional (`Shift`), desde el
  centro (`Alt`) y **rotar**.
- **Imanes de alineación** con guías vivas (bordes, centros y elementos vecinos).
- **Cuadrícula magnética** configurable (1 %, 2.5 %, 5 %, 10 %); `Alt` la ignora.
- **Multiselección** con `Shift`/`Ctrl` + clic o `Ctrl+A`; arrastre, borrado,
  duplicado y desplazamiento por flechas de todo el conjunto.
- **Distribuir** en fila o columna, **igualar** ancho / alto / ambos, **alinear el
  conjunto** por cualquier borde, **rejilla automática** de N columnas.
- **Agrupar y desagrupar** (`Ctrl+G` / `Ctrl+Shift+G`): un grupo se mueve, escala y
  gira como una sola pieza; sus hijos pasan a coordenadas relativas, por lo que la
  composición interna no se deforma. Un segundo clic dentro de un grupo ya
  seleccionado entra a editar la pieza interior.
- **Portapapeles de elementos** (`Ctrl+C` / `Ctrl+X` / `Ctrl+V`) que funciona
  **entre láminas y entre presentaciones distintas** y sobrevive a la recarga, más
  **pegar solo el estilo** (`Ctrl+Shift+V`).
- **Reemplazo real de imágenes** (archivo local reescalado a máx. 1680 px, o URL) y
  **de figuras** (rectángulo, píldora, elipse, línea) conservando la geometría.
- **Inspector completo**: geometría numérica, rotación, opacidad, radio, sombra,
  relleno, borde y grosor, color y tamaño de texto, alineación, ajuste de imagen,
  bloqueo y capas.
- Volver al flujo automático en cualquier momento.

Modelo de datos: todo vive en el HTML de la lámina
(`.slide-inner.has-free` → `.free-canvas.is-freed` → `.free-el[data-lay-type]` con
`left/top/width/height` en % y `data-rot` en grados), por lo que se guarda, se
comparte y se exporta sin trabajo adicional. La interfaz de selección vive en una
capa hermana `.lay-ui` para no contaminar el HTML guardado.

### 3.6 Biblioteca multi‑presentación

Pestaña **Presentaciones** del Studio: crear una presentación nueva (en blanco,
desde una plantilla o copiando la actual), renombrar, duplicar, eliminar y abrir.
Cada deck guarda su propio contenido, diseño y ajustes.

### 3.7 Zoom y paneo por áreas

HUD con acercar, alejar, **lupa de área** (`Z`, se arrastra un recuadro sobre la
zona a ampliar) y vista completa (`0`). Paneo arrastrando cuando hay zoom.
Implementado como `transform: scale() translate()` sobre `#deck`, de modo que los
consumidores de `getBoundingClientRect()` siguen funcionando sin cambios.
**Disponible también para los invitados.**

### 3.8 Compartición sin servidor

La presentación completa se serializa a JSON, se comprime con
`CompressionStream('deflate-raw')`, se codifica en base64url y se coloca en el
**fragmento de la URL** (`#d=`). Nada sale del dispositivo: el fragmento no se
envía en la petición HTTP.

- Enlace directo con copia al portapapeles.
- **WhatsApp**, Telegram, correo, compartir nativo del sistema y **código QR**
  (generado solo al pulsarlo).
- Interruptores **«el invitado puede editar»** y **«quitar imágenes incrustadas»**
  para obtener enlaces ligeros.
- **Modo invitado**: barra de aviso propia, opción de crear una copia propia
  (*fork*) y de salir.
- Compresión medida: **71 % menos** (73 896 → 21 411 caracteres).

### 3.9 Historial de edición

Instantáneas por lámina con `Ctrl+Z` / `Ctrl+Shift+Z` / `Ctrl+Y` y barra visual con
contador. Escucha en **fase de captura** y solo actúa con `body.edit-mode`, para no
colisionar con el deshacer de trazo de la pizarra.

### 3.10 Paleta de comandos universal (`Ctrl+K`)

Centro de mando único, al estilo de VS Code o Figma. Busca simultáneamente en
**tres universos**:

1. **Acciones** — las ~60 capacidades de la aplicación.
2. **Diapositivas** — salto directo buscando por título, sección o cuerpo del texto.
3. **Presentaciones** — abre cualquier deck de la biblioteca.

Búsqueda **difusa** con puntuación por relevancia (prefijo exacto, palabra
completa, subsecuencia) y **aprendizaje de frecuencia de uso**: las acciones más
utilizadas ascienden solas. Navegación íntegra con teclado. Las acciones cuyo
módulo no está cargado no aparecen. Otros módulos pueden añadir acciones con
`StargateCommand.register()`.

### 3.11 Nube: enlaces cortos reales y revisión con comentarios

Esta sección **requiere el sitio publicado**, porque usa la base de datos D1 que el
despliegue provisiona en Cloudflare. En la vista previa local el módulo lo detecta y
lo avisa con claridad, en lugar de fallar en silencio.

> La pizarra «Presentar un proyecto» tiene su **propia nube equivalente**
> (`js/wb-cloud.js`, tabla `boards`), con el mismo enlace corto y la misma
> condición de «solo en el sitio publicado» — ver §3.19.

**Enlaces cortos reales.** Antes la presentación viajaba entera en el fragmento de
la URL: más de 20 000 caracteres que WhatsApp truncaba. Ahora el deck se guarda en la
base de datos y el enlace queda así:

```
https://tu-dominio/index.html#s=7QK2M9
```

Medido en pruebas: **20 877 → 115 caracteres, un 99.4 % más corto**, y **cabe en un
SMS de 160**. Con eso el QR también se vuelve pequeño y legible de lejos.

**Publicación actualizable.** Republicando con el **mismo código** se incrementa el
número de revisión y quien ya tenga el enlace ve la versión nueva sin que le
reenvíes nada. Verificado: no se duplica el registro.

**Comentarios de revisión anclados.** Los invitados fijan notas a un punto concreto
de una lámina (coordenadas en % del lienzo, por lo que aguantan cualquier
redimensión). Se ven como chinchetas numeradas; al pulsarlas se abre la burbuja con
autor, texto y acciones de resolver o eliminar. El panel lista todos los
comentarios y permite saltar a la lámina correspondiente. Esta es la **revisión
asíncrona** que sustituye a la colaboración en vivo, que sí requeriría WebSocket.

> **Privacidad — la interfaz lo advierte antes de publicar:** una presentación
> publicada con enlace corto queda **guardada en la base de datos del sitio** y
> **cualquiera que conozca o adivine su código de 6 caracteres puede abrirla**. No
> hay contraseña ni cifrado. Para material confidencial hay que usar el enlace largo
> (`#d=`), que no toca ningún servidor, o no publicar.
>
> El nombre del revisor es solo una firma guardada en el dispositivo: **no es una
> cuenta ni una identidad verificada**, y cualquiera puede escribir con el nombre
> que quiera. Un sitio estático no puede autenticar de verdad a nadie.

### 3.12 Exportación e importación

Panel rediseñado (`X`):

- **Formato** en tarjetas: **PDF**, **PPTX**, **Google Slides**, **Imágenes PNG**,
  **Guion del orador**.
- **Rango de páginas**: todas `(1-N)`, solo la actual, solo con marcaje `(n)` o
  **rango personalizado** (`1-3, 5, 8-10`; acepta `a`, tolera rangos invertidos y
  descarta páginas fuera de límites; contador en vivo y aviso en rojo si no hay
  ninguna válida).
- **Opciones avanzadas** plegadas: incluir la tinta, forzar tema, resolución
  (hasta 4K).
- Un solo botón grande **Exportar …** cuyo texto cambia con el formato.
- **Google Slides**: un sitio estático no puede crear presentaciones en la cuenta
  del usuario sin OAuth, así que se resuelve en **dos pasos guiados**: descargar el
  `.pptx` (con el rango elegido y las notas del orador) y abrir Slides en el diálogo
  «Abrir archivo → Subir», que lo convierte en una presentación editable.
- El nombre del archivo sale del nombre de la baraja activa.
- **Importación** de PowerPoint, PDF y texto, con generación automática de láminas.

### 3.13 Menú principal con todas las herramientas (`F1` / `Ctrl+M`)

Un único menú declarativo agrupado (Ver y navegar, Zoom, Grabar y deshacer, Editar,
Maquetar, Compartir, Marcar, Sistema) con **búsqueda** por nombre, grupo, palabras
clave y atajo. Cada entrada pulsa el botón real correspondiente, sin duplicar
lógica. Los invitados solo ven las entradas marcadas como seguras.

### 3.14 Grabar, deshacer global y puntos de restauración

- **Grabar** (`Ctrl+S`) con indicador de estado (punto + «cambios sin grabar») y
  aviso `beforeunload` si hay cambios pendientes.
- **Deshacer / rehacer global** (`Ctrl+Z` / `Ctrl+Y` fuera del modo edición):
  instantáneas de la baraja completa restauradas con `StargateContent.replaceActive()`.
  Cubre crear presentación, reordenar, borrar láminas, notas del orador, etc. El
  deshacer por lámina del Studio (`history.js`) sigue activo en edición.
- **Puntos de restauración** con nombre (`stargate.restore.v1`).

### 3.15 Visor de invitado (enlaces compartidos)

Al abrir `#d=` o `#s=` el invitado **solo ve la presentación**: se ocultan Studio,
importar, exportar, compartir, comentarios, la pizarra y el botón Presentar. Barra
inferior con **anterior / siguiente, alejar / acercar, vista completa, girar y
pantalla completa**. Portada de arranque que pide el gesto necesario para la
**pantalla completa**, y **orientación automática**: 16:9 si la ventana es más ancha
que alta (PC) y 9:16 si es más alta (teléfono), recalculada al girar el dispositivo.
*Swipe* para cambiar de lámina cuando no hay zoom.

### 3.16 Enlaces cortos sin servidor (además de los de la nube)

El enlace `#d=` ahora viaja **adelgazado** (sin `oracle`, sin campos vacíos ni
espacios redundantes) y **diferencial**: las láminas idénticas a las de fábrica se
sustituyen por su índice (`base:'factory'`). Medido: **20 884 → 409 caracteres
(−98 %)** para una baraja basada en la de fábrica, y **1 821** para una creada desde
cero con el asistente. Cabe en un SMS largo y en un QR pequeño.

### 3.17 Asistente «Crear nueva presentación» (`Ctrl+Alt+N`)

Botón **+** en el riel, entrada en menú y paleta. Cuatro pasos: **tipo** (11:
plan de campaña, informe, propuesta, discurso, pitch, capacitación, análisis
territorial, agenda, comparativo, hoja en blanco, copiar la actual), **datos**
(nombre obligatorio, subtítulo, autor, fecha), **aspecto** (plantilla, formato,
día / noche) y **láminas** (esqueleto sugerido con casillas para quitar las que no
hagan falta). Genera láminas con las clases reales de `style.css`, activa la baraja
nueva, aplica formato y tema, marca un punto de deshacer y abre el Studio.

### 3.18a Transiciones entre láminas (`Studio → Diseño`)

Siete transiciones seleccionables en una rejilla con **vista previa animada**
(cada tarjeta reproduce su propio efecto en miniatura al abrir la pestaña):

| Clave | Nombre | Efecto |
|---|---|---|
| `rise` | Ascenso | Sube con desenfoque (la transición original, por defecto) |
| `fade` | Fundido | Aparece y desaparece suavemente |
| `slide` | Deslizar | Entra desde la derecha |
| `zoom` | Zoom | Crece desde el centro |
| `flip` | Giro | Gira sobre el eje vertical |
| `drop` | Caída | Cae desde arriba con rebote |
| `none` | Ninguna | Cambio instantáneo |

Se eligen desde `Studio → Diseño` (rejilla `#tr-grid`) o desde la **paleta de
comandos** (7 entradas `tr.*`). Se guardan **por presentación** (`cur.transition`),
se aplican como `body.dataset.transition` (CSS puro en `css/presenter.css`, solo
modifica `transform`/`opacity` de `.slide` / `.slide.is-active` / `.slide.leaving`,
sin tocar el resto del maquetado) y **viajan en los enlaces compartidos**
(`sharePayload()` / `replaceActive()`), por lo que el invitado ve la misma
transición que el autor. Catálogo expuesto en `StargateContent.TRANSITIONS` /
`StargateContent.TR_ORDER`.

### 3.18 Otras piezas de esta iteración

- **Reordenar láminas arrastrando** en la pestaña Láminas (asa, línea de inserción,
  botones subir / bajar / borrar; en táctil se ocultan las asas).
- **Arsenal de componentes**: guardar la selección del maquetador con nombre y
  etiqueta, insertarla en cualquier lámina o presentación, exportar / importar JSON.
- **Modo sala sin conexión**: `sw.js` cachea la aplicación y los CDN; `tables/`
  nunca se cachea. Panel en la pestaña Datos. Requiere HTTPS (sitio publicado).

### 3.19 «Presentar un proyecto» — pizarra infinita tipo Excalidraw

Herramienta **completamente separada** de «Crear presentaciones»: página propia
(`proyecto.html`), motor propio (`js/whiteboard.js`, expone `window.WB`), UI propia
(`js/whiteboard-ui.js`, expone `window.WBUI`) y hoja de estilos propia
(`css/whiteboard.css`). Pensada para **construir, exponer y ejemplificar proyectos**
en un lienzo sin límites, al estilo Excalidraw.

**Lienzo y cámara.** Zoom y paneo continuos (`cam = {x,y,z}`), cuadrícula de puntos
opcional, ajustar todo a la vista, ajustar a la selección. El lienzo se **reencuadra
solo cuando hace falta** al abrir un proyecto o al cambiar el tamaño de la ventana
(por ejemplo, si se guardó en una pantalla grande y se abre luego en un teléfono),
para que el contenido nunca aparezca fuera de pantalla ni ilegible.

**Herramientas** (barra superior con atajo de teclado en cada una): seleccionar,
mano (`H`), rectángulo (`R`), rombo (`D`), elipse (`O`), flecha (`A`), línea (`L`),
dibujo a mano libre (`P`), texto (`T`), nota adhesiva (`N`), imagen, marco / *frame*
(`F`) y borrador (`E`). Trazado **con aspecto dibujado a mano** (*rough*, 3 niveles
de aleatoriedad), colores, grosores, relleno, esquinas, opacidad, capas, agrupar,
bloquear, duplicar — todo desde el panel de propiedades a la izquierda (se acopla
abajo en pantallas de teléfono).

**Marcos = modo presentación por pasos.** Cada *frame* que se numera pasa a ser un
paso de la presentación: al entrar en **modo presentar**, la cámara encuadra cada
marco en su turno (barra inferior con contador, anterior / siguiente, ajustar,
pantalla completa y salir), y se puede **dibujar y usar el puntero láser sobre la
marcha** mientras se presenta, igual que en la pizarra táctica del deck.

**8 plantillas** listas para empezar (`WBUI.templates`): en blanco, diagrama de
flujo, arquitectura, hoja de ruta, mapa mental, *kanban*, *pitch* (con marcos
numerados de presentación) y análisis DOFA/SWOT.

**Multi‑proyecto.** Biblioteca de proyectos con crear, renombrar, duplicar y
eliminar, igual que la biblioteca de presentaciones del otro lado.

**Compartir sin servidor.** Igual filosofía que el deck: el proyecto se serializa,
se comprime con `deflate-raw` y se codifica en base64url en el fragmento
`proyecto.html#p=<carga>` — nada sale del dispositivo. Un serializador «ligero»
omite las propiedades que tienen su valor por defecto para acortar el enlace. Al
abrir un enlace recibido se crea automáticamente un proyecto nuevo con la etiqueta
«(recibido)», sin pisar ningún proyecto propio.

**Enlace corto por nube** (`js/wb-cloud.js`, tabla D1 `boards` — solo en el sitio
publicado, igual condición que §3.11): en el modal Compartir, además del enlace
largo `#p=`, un botón **Publicar** guarda el proyecto en la base de datos y genera
un enlace de la forma `proyecto.html#w=7QK2M9` (unos 40 caracteres, cabe en un
SMS). Republicar con el **mismo código** actualiza el registro (incrementa la
revisión) en vez de duplicarlo. «Abrir por código corto…» en el menú principal
recupera cualquier proyecto publicado escribiendo su código de 6 caracteres.
Medido en pruebas: enlace largo de 874 caracteres → **118 con el corto (−86,5 %)**.
Misma advertencia de privacidad que los decks: **sin contraseña, cualquiera que
conozca o adivine el código puede abrirlo.**

**Exportar** a PNG, SVG, **PDF** y **PowerPoint (.pptx)**, además de JSON (respaldo /
intercambio), desde el menú principal. Para PDF y PPTX, **cada marco numerado se
convierte en una página / diapositiva** (recortada a su encuadre, con su título
como nota del orador); si el proyecto no tiene marcos, toda la escena se exporta
como una sola página. Usa los mismos motores que «Crear presentaciones»
(`jsPDF`, `PptxGenJS`, cargados solo al exportar) pero rasteriza directamente
desde el **modelo vectorial propio de la pizarra** (`W.drawElement`), sin pasar
por `html2canvas` — la pizarra no tiene un DOM equivalente que rasterizar.

**Navegación cruzada entre herramientas** (sin mezclarlas en una sola pantalla):

- `inicio.html` — portada con dos tarjetas grandes (**Presentar un proyecto** /
  **Crear presentaciones**) y una lista de «recientes» leída de ambos
  almacenamientos locales.
- En `index.html`, el riel derecho tiene los botones **Inicio** (🏠) y **Presentar
  un proyecto** (📋), y el menú principal (`F1`) tiene las entradas «Presentar un
  proyecto (pizarra infinita)» y «Volver al inicio». Ambos botones se ocultan en
  modo invitado (`body.viewer-mode`).
- En `proyecto.html`, el riel derecho enlaza de vuelta a **Crear presentaciones**
  (`index.html`).

---

## 4. URIs y puntos de entrada

Sitio estático; no hay API propia ni base de datos.

| Ruta | Descripción |
|---|---|
| `inicio.html` | **Portada**: elegir entre «Crear presentaciones» y «Presentar un proyecto» |
| `index.html` | Aplicación completa — Crear presentaciones (deck) |
| `index.html#N` | Abre directamente en la lámina `N` (1‑indexada) |
| `index.html#d=<carga>` | Abre una presentación compartida (comprimida, sin servidor) |
| `index.html#j=<carga>` | Igual, sin compresión (respaldo) |
| `index.html#s=<código>` | **Enlace corto**: abre la presentación publicada en la nube |
| `proyecto.html` | **Presentar un proyecto**: pizarra infinita tipo Excalidraw |
| `proyecto.html#p=<carga>` | Abre un proyecto de pizarra compartido (comprimido, sin servidor) |
| `proyecto.html#w=<código>` | **Enlace corto**: abre el proyecto de pizarra publicado en la nube |
| `video-brief.html` | Brief audiovisual complementario |
| `test-layout.html` | **Banco de pruebas del maquetador** (ver §7) |
| `test-cloud.html` | **Banco de pruebas de la nube** (ver §7) |
| `test-slides.html` | Banco: reordenar láminas arrastrando |
| `test-suite.html` | Banco: menú, grabar / deshacer, puntos de restauración |
| `test-creator.html` | Banco: asistente de nueva presentación, enlace diferencial, visor |
| `test-present.html` | Banco: barra de marcaje, botón Presentar, exportación nueva |
| `test-presenter-win.html` | Banco: **ventana real del presentador** (sincronía, dibujo remoto, notas) |
| `test-whiteboard.html` | Banco: **pizarra infinita** — herramientas, plantillas, presentación por marcos, enlace |
| `test-wb-export.html` | Banco: **exportación de la pizarra a PDF y PowerPoint** |
| `test-wb-cloud.html` | Banco: **enlace corto de la pizarra** (publicar, republicar, abrir por código) |
| `sw.js` | Service worker (modo sala) |

### API de datos consumida (solo en el sitio publicado)

| Método y ruta | Uso |
|---|---|
| `GET tables/decks?search=<código>` | Resolver un enlace corto de un deck |
| `POST tables/decks` | Publicar una presentación |
| `PUT tables/decks/<id>` | Actualizar una revisión |
| `PATCH tables/decks/<id>` | Incrementar el contador de visitas |
| `GET tables/comments?search=<código>` | Cargar los comentarios de un deck |
| `POST tables/comments` | Crear un comentario anclado |
| `PATCH tables/comments/<id>` | Marcar resuelto o reabrir |
| `DELETE tables/comments/<id>` | Eliminar un comentario |
| `GET tables/boards?search=<código>` | Resolver un enlace corto de un proyecto de pizarra |
| `POST tables/boards` | Publicar un proyecto de pizarra |
| `PUT tables/boards/<id>` | Actualizar una revisión |
| `PATCH tables/boards/<id>` | Incrementar el contador de visitas |

### Atajos de teclado

| Tecla | Acción |
|---|---|
| `F1` / `Ctrl+M` | **Menú principal con todas las herramientas** |
| `Ctrl+K` / `Ctrl+Shift+P` | **Paleta de comandos** |
| `Ctrl+Alt+N` | **Crear nueva presentación** |
| `Ctrl+S` | **Grabar cambios** |
| `Ctrl+Z` / `Ctrl+Y` (fuera de edición) | **Deshacer / rehacer global** |
| `←` `→` `Inicio` `Fin` | Navegación |
| `N` | **Presentar** (desplegable) |
| `O` · `F` · `H` · `S` · `P` | Índice · Completa · Limpio · Foco · Autoavance |
| `M` · `Q` · `B` | Mano · Rombo · **Candado** (fijar herramienta) |
| `D` · `R` · `+` `−` | Tema · Formato · Escala de texto |
| `Z` · `0` | Lupa de área · Restablecer zoom |
| `K` | Compartir |
| `X` · `Ctrl+P` · `I` | Exportar · PDF directo · Importar |
| `1`–`0` · `C` | Herramientas de marcaje · Borrar tinta |
| `Ctrl+Shift+E` | Studio |
| `Ctrl+Z` / `Ctrl+Shift+Z` | Deshacer / rehacer edición |
| `Ctrl+A` · `Ctrl+G` · `Ctrl+Shift+G` | Seleccionar todo · Agrupar · Desagrupar |
| `Ctrl+C` `Ctrl+X` `Ctrl+V` `Ctrl+Shift+V` | Portapapeles de elementos · Solo estilo |
| `Ctrl+D` · `Supr` · `[` `]` · `Tab` | Duplicar · Borrar · Capas · Elemento siguiente |

#### Atajos propios de `proyecto.html` (Presentar un proyecto)

Mismo teclado de herramientas de dibujo (letra inicial de cada herramienta: `V`
seleccionar, `H` mano, `R` rectángulo, `D` rombo, `O` elipse, `A` flecha, `L` línea,
`P` dibujo libre, `T` texto, `N` nota, `F` marco, `E` borrador), más `Ctrl+Z` /
`Ctrl+Shift+Z` deshacer·rehacer, `Ctrl+D` duplicar, `Ctrl+G` agrupar, `Supr` borrar,
`+`/`-` zoom y `0` ajustar todo. Independiente del teclado de `index.html`.

---

## 5. Modelo de datos y almacenamiento

Hay **dos almacenamientos, con propósitos distintos**.

### 5.1 Base de datos del sitio publicado (Cloudflare D1)

Solo se usa para la nube (§3.11 del deck y §3.19 de la pizarra). Definida en
`.tables/schema.json`, con **tres tablas independientes** (`decks`, `comments`,
`boards`): un código corto de un deck y uno de un proyecto de pizarra nunca se
cruzan, aunque coincida el alfabeto con el que se generan.

**Tabla `decks`** — presentaciones publicadas con enlace corto:

| Campo | Tipo | Contenido |
|---|---|---|
| `code` | text | Código corto público (p. ej. `7QK2M9`) |
| `name` | text | Nombre de la presentación |
| `payload` | rich_text | Deck completo comprimido en base64url |
| `can_edit` | bool | Si el invitado puede editar su copia |
| `slides` | number | Número de láminas |
| `views` | number | Veces que se abrió el enlace |
| `owner` | text | Firma libre de quien publicó |
| `revision` | number | Revisión, se incrementa al republicar |
| `updated_label` | text | Última actualización, legible |

**Tabla `comments`** — comentarios de revisión anclados:

| Campo | Tipo | Contenido |
|---|---|---|
| `deck_code` | text | Código de la presentación comentada |
| `slide` | number | Índice de la lámina (0-indexado) |
| `author` | text | Firma de quien comenta |
| `body` | text | Texto del comentario |
| `x`, `y` | number | Posición del marcador, en % del lienzo |
| `resolved` | bool | Si ya fue atendido |

**Tabla `boards`** — proyectos de pizarra publicados con enlace corto (§3.19):

| Campo | Tipo | Contenido |
|---|---|---|
| `code` | text | Código corto público (p. ej. `7QK2M9`) |
| `name` | text | Nombre del proyecto de pizarra |
| `payload` | rich_text | Proyecto comprimido en base64url (mismo formato que `#p=`) |
| `can_edit` | bool | Si el invitado puede editar su copia |
| `elements` | number | Número de elementos, para mostrar en listas |
| `views` | number | Veces que se abrió el enlace |
| `owner` | text | Firma libre de quien publicó |
| `revision` | number | Revisión, se incrementa al republicar |
| `updated_label` | text | Última actualización, legible |

> Los códigos (de `decks` y de `boards`) se generan con
> `crypto.getRandomValues()` sobre un alfabeto de 26 caracteres sin ambigüedades
> (sin `O`/`0`, `I`/`1`, `S`/`5`), para poder dictarlos por teléfono sin errores.

**Dos bases separadas que nunca se sincronizan:** la de la vista previa del editor y
la del sitio desplegado. Un deck o un proyecto de pizarra publicado desde la vista
previa **no** aparece en el sitio en vivo, y al contrario.

### 5.2 Almacenamiento local del dispositivo

Todo lo demás —contenido, diseño, biblioteca, preferencias— vive en `localStorage`:

| Clave | Contenido |
|---|---|
| `stargate.library.v1` | Biblioteca de presentaciones (contenido, diseño, metadatos) |
| `stargate.content.v1` | Contenido del deck (legado v5, migrado automáticamente) |
| `stargate.prefs.v1` | Preferencias de vista (tema, formato, escala, color, grosor) |
| `stargate.clip.v1` | Portapapeles de elementos del maquetador |
| `stargate.cmd.use.v1` | Frecuencia de uso de la paleta de comandos |
| `stargate.cloud.me.v1` | Nombre con el que firmas los comentarios |
| `stargate.cloud.mine.v1` | Códigos publicados desde este dispositivo |
| `stargate.components.v1` | Arsenal de componentes reutilizables |
| `stargate.restore.v1` | Puntos de restauración con nombre |
| `stargate.ink.v1` | Tinta de la pizarra por lámina |
| `stargate.studio.session` | Sesión desbloqueada del Studio (`sessionStorage`) |

`stargate.prefs.v1` incluye ahora `sticky` (candado de herramienta).

**Almacenamiento propio de `proyecto.html`** (independiente de todo lo anterior):

| Clave | Contenido |
|---|---|
| `stargate.wb.v1` | Biblioteca de proyectos de pizarra: `{activeId, boards:{[id]:{id,name,created,updated,elements:[],cam:{x,y,z}}}}` |
| `stargate.wb.clip.v1` | Portapapeles de elementos de la pizarra |
| `stargate.wb.theme` | Tema día / noche de la pizarra |
| `stargate.wb.seen` | Si ya se mostró el modal de plantillas a un proyecto vacío |
| `stargate.wb.cloud.mine.v1` | Códigos de proyectos de pizarra publicados desde este dispositivo |

Estructura de una lámina:

```js
{
  kicker: 'Texto de sección',
  title:  'Título (admite HTML)',
  html:   'Cuerpo de la lámina',
  bg: 'URL o data:',   bgDim: 0.62,
  cover: false, finale: false,
  oracle: { summary: '', edge: '', narration: '' }
}
```

### Eventos personalizados

`stargate:slide` · `stargate:relayout` · `stargate:zoom` · `stargate:theme` ·
`stargate:persist-slide` · `stargate:layout-changed` · `stargate:imported` ·
`stargate:studio-open` · `stargate:tool` (herramienta de marcaje seleccionada)

`proyecto.html` no comparte estos eventos: es un documento aparte con su propio
bucle de render (`WB.render()`) invocado directamente por `whiteboard-ui.js`, sin
necesidad de un bus de eventos entre módulos.

---

## 6. Estado de verificación

Todas las pruebas ejecutadas en navegador real (Playwright) con **cero errores de
consola**, más capturas verificadas en **escritorio (1280 px)** y **teléfono
(390 px)**.

| Prueba | Resultado |
|---|---|
| Módulos cargados | Layout · Zoom · Share · Content · History · Command · Cloud ✔ |
| Liberación granular | **9 átomos** (antes 2) ✔ |
| Elemento seleccionado al arrastrar un título | `H2` (no el `HEADER`) ✔ |
| Movimiento real | `0 %, 3.07 %` → `37.08 %, 18.81 %` ✔ |
| Inspector | Presente, visible y sincronizado (X 37 · Y 19) ✔ |
| Reemplazo de figura | elipse → píldora ✔ |
| Zoom | 2.50× · HUD «250 %» · reset a 1 ✔ |
| Enlace compartido | `#d=` · 71 % de compresión ✔ |
| Multiselección | 10 elementos ✔ |
| Distribuir · rejilla · cuadrícula | ✔ |
| Historial | profundidad 6 · deshacer y rehacer ✔ |
| Agrupar | 10 elementos → 1 grupo, movido como pieza única ✔ |
| Desagrupar | 1 grupo → 10 elementos, sin deformación ✔ |
| Portapapeles | copiar, pegar (10 → 11) y pegar solo estilo ✔ |
| Paleta de comandos | 73 acciones · búsqueda difusa · ejecución por teclado ✔ |
| **Enlace corto** | 20 877 → **115 caracteres · 99.4 % más corto** · cabe en SMS ✔ |
| Republicar mismo código | revisión 1 → 2, **sin duplicar el registro** ✔ |
| Recuperar de la base | nombre, 20 láminas y `can_edit` correctos ✔ |
| Comentario anclado | creado en lámina 3 en `x 42.5 % · y 61.25 %` ✔ |
| Marcadores y burbuja | 1 chincheta en la posición exacta · burbuja abre ✔ |
| Resolver y eliminar | resuelto `true` · eliminado, quedan 0 ✔ |
| Abrir por código corto | modo invitado activo · 20 láminas ✔ |
| Reordenar arrastrando | orden de títulos correcto · asa y línea de inserción visibles ✔ |
| Menú principal | 64 entradas / 8 grupos · búsqueda por nombre, grupo, claves y atajo ✔ |
| Grabar / deshacer global | deshacer y rehacer de creación · punto de restauración ✔ |
| Enlace diferencial | 20 884 → **409 caracteres (−98 %)** · 20 láminas idénticas al abrir ✔ |
| Visor de invitado | barra visible · Studio y botón nueva ocultos ✔ |
| Asistente | 11 tipos · bloqueo sin nombre · 8 láminas generadas · 0 clases sin CSS ✔ |
| Barra de marcaje | 13 herramientas · 13 badges · rombo dibujado · vuelve al puntero · candado mantiene ✔ |
| Botón Presentar | 4 opciones · `N` abre · clic fuera y `Esc` cierran ✔ |
| **Ventana del presentador** | lámina y siguiente renderizadas · Siguiente mueve la principal y viceversa · **trazo remoto llega a la pizarra** · notas grabadas en la lámina · cronómetro ✔ |
| Exportación | 5 formatos · `parsePages('1-3, 5, 8-10')` → `[0,1,2,4,7,8,9]` · inválido en rojo · modal Google Slides ✔ |
| Transiciones | 7 variantes · selección persiste por deck · viaja en `sharePayload()` · `none` sin duración CSS ✔ |
| Capturas | barra vertical con badges, píldora superior en 16:9, desplegable y exportación en escritorio **y** teléfono ✔ |
| **Pizarra infinita** (`test-whiteboard.html`) | 14 herramientas · texto en figura y suelto · mover/redimensionar · marquesina · duplicar/agrupar/capas/borrar · deshacer/rehacer · zoom/cuadrícula/candado · estilos · borrador (corregido) · láser · plantilla *pitch* (15 elementos, 5 marcos numerados) · modo presentar entra/navega/sale · enlace `#p=` ida y vuelta (~980 car.) · exportar JSON, SVG (3501 B), **PDF y PPTX** (motores `jsPDF`/`PptxGenJS` cargados y ejecutados sin error, un marco = una página) · CRUD de proyectos ✔ |
| Pizarra — ventana del presentador | dibujo remoto, láser y borrador llegan a la pizarra principal desde la ventana emergente ✔ |
| Pizarra — encuadre automático | contenido completo visible tras abrir un proyecto guardado en otro tamaño de pantalla, en escritorio, tableta **y** teléfono (técnica de iframe de tamaño fijo) ✔ |
| Navegación cruzada | `inicio.html` → ambas tarjetas abren la herramienta correcta · botones Inicio / Presentar un proyecto visibles en `index.html` y ocultos en modo invitado · enlace de vuelta desde `proyecto.html` ✔ |
| **Nube de la pizarra** (`test-wb-cloud.html`) | publicado · código de 6 caracteres · enlace de **118 caracteres** (874 → 118, **−86,5 %**) · cabe en SMS · recuperado de la base con nombre y elementos correctos · republicar mismo código → revisión 1→2 **sin duplicar** · abrir por código crea proyecto «(recibido)» con los mismos elementos ✔ |

### Defectos encontrados por las pruebas y corregidos

1. **Marcadores de comentario invisibles.** `publish()` no fijaba el código activo
   del deck, así que `addComment()` rechazaba las notas por no saber a qué
   presentación pertenecían. Ahora publicar activa el deck de inmediato.
2. **HUD de zoom solapado en teléfono.** La captura móvil reveló que pisaba el pie
   de la lámina; se reubicó en vertical sobre el borde derecho. No era visible en
   escritorio, donde el `hover` oculta el HUD.
3. **Barra de marcaje recortada a 800 px de alto.** Al añadir candado / mano /
   rombo la barra superó la altura disponible; se agregó una regla compacta
   `@media (min-width:821px) and (max-height:900px)`.
4. **Botones de exportación antiguos seguían visibles.** `.ex-grid[hidden]` no
   ocultaba nada porque `display:grid` ganaba sobre `[hidden]`; se forzó
   `display:none!important` junto al atributo.
5. **Pizarra: el panel de propiedades tapaba el diagrama al «ajustar a la vista».**
   `fitTo()` calculaba contra el lienzo completo sin descontar el panel izquierdo.
   Se introdujo `freeArea()` (área visible real, restando cabecera/panel/barra) y
   se hizo que `fitTo`/`fitAll` encuadren contra ella.
6. **Pizarra: margen fijo de ajuste ilegible en teléfono.** Un margen fijo de
   60–80 px es enorme en una pantalla pequeña. Se hizo proporcional al área libre
   (`min(56, min(ancho,alto) * 0.05)`), verificado con 90–95 % de relleno en
   teléfono, tableta y escritorio.
7. **Pizarra: proyecto guardado se abría fuera de encuadre en otra pantalla.** Se
   añadieron `visibleFraction()` / `contentVisible()` / `fillFraction()` /
   `fitIfNeeded()`, invocadas al abrir un proyecto, al cargar la página y al
   redimensionar la ventana (con reintento a 350 ms por si la fuente tardó en
   asentarse).
8. **Pizarra: el borrador no borraba figuras huecas por el centro.** `hitEl()`
   solo detectaba el borde de figuras sin relleno. Se añadió un modo «solid» de
   impacto que el borrador usa como respaldo (`pick(p) || pick(p, true)`).
9. **Bug del banco de pruebas (no de la app):** `js/test-creator.js` leía
   `C.library().decks`, pero `library()` devuelve un **arreglo**, no un objeto con
   `decks`. Corregido en las 3 ocurrencias; el asistente ya pasaba end-to-end.

---

## 7. Banco de pruebas de desarrollo

Dos bancos independientes, ambos cargan `index.html` en un iframe y entran al Studio
por la puerta real:

- **`test-layout.html`** + `js/test-layout.js` — simula **gestos `PointerEvent`
  auténticos** para comprobar el maquetador, el zoom, la compartición, los grupos,
  el portapapeles y la paleta de comandos.
- **`test-cloud.html`** + `js/test-cloud.js` — publica un deck, lo recupera por
  código, lo republica, y crea, resuelve y elimina un comentario contra la base de
  datos real.

- **`test-slides.html`**, **`test-suite.html`**, **`test-creator.html`**,
  **`test-present.html`**, **`test-presenter-win.html`** — bancos de las piezas
  añadidas en la última iteración (ver §6).
- **`test-whiteboard.html`** + `js/test-whiteboard.js` — carga **`proyecto.html`**
  directamente (no `index.html`): recorre las 14 herramientas, la edición de texto,
  mover/redimensionar, marquesina, duplicar/agrupar/capas/borrar, deshacer/rehacer,
  zoom/cuadrícula/candado, estilos, borrador, láser, carga de plantillas, marcos y
  modo presentación, enlace `#p=`, exportar JSON/SVG/PDF/PPTX y CRUD de proyectos.
- **`test-wb-export.html`** + `js/test-wb-export.js` — carga **`proyecto.html`**,
  añade la plantilla *pitch* (5 marcos) y ejecuta `exportPDF()` / `exportPPTX()`
  comprobando que los motores (`jsPDF`, `PptxGenJS`) se cargan y ejecutan sin
  errores de consola, tanto con la pizarra vacía como con contenido.
- **`test-wb-cloud.html`** + `js/test-wb-cloud.js` — publica un proyecto de
  pizarra, lo recupera por código, lo republica (comprueba que no se duplica) y
  lo reabre por código corto, contra la base de datos real (tabla `boards`).

Se conservan deliberadamente como red de seguridad. **No están enlazados desde
`index.html`** ni `proyecto.html`, y pueden eliminarse sin afectar la aplicación:

```
rm test-*.html js/test-*.js
```

> Los bancos de nube (`test-cloud.html` y `test-wb-cloud.html`) **escriben
> registros reales**. Tras ejecutarlos conviene limpiar las tablas `decks`,
> `comments` y `boards` para no dejar basura de prueba.

---

## 8. Pendiente / no implementado

### Resuelto en esta iteración (ya no es pendiente)

- ~~Enlaces cortos~~ → **hecho** con la base de datos D1: 99.4 % más cortos (§3.11).
- ~~Colaboración~~ → resuelta en su forma **asíncrona**: comentarios de revisión
  anclados a coordenadas, que cubren el caso real de «que otro me marque
  observaciones sobre la presentación».

### Sigue sin implementar, y por qué

- **Colaboración simultánea en vivo** (ver el cursor del otro moviéndose en tiempo
  real): necesita un canal persistente **WebSocket** y resolución de conflictos
  **CRDT**. La base de datos D1 permite leer y escribir, pero no empujar cambios al
  navegador del otro: seguiría siendo *sondeo*, no tiempo real. Es la única de las
  tres imposibilidades originales que sigue en pie.
- **Seguridad real del Studio y de los enlaces publicados.** La clave del Studio se
  comprueba en el navegador y los códigos cortos no tienen contraseña. Hacer esto
  bien exige autenticación de servidor y control de acceso por identidad; el
  proyecto tiene el descriptor de acceso disponible si algún día se necesita.
- **Identidad verificada de los revisores.** Hoy la firma es un nombre libre
  guardado en el dispositivo.
- **Google Slides directo** (crear la presentación en la cuenta del usuario): exige
  OAuth de Google y la API de Slides desde un servidor. Se resuelve con el flujo de
  dos pasos (`.pptx` → abrir en Slides).
- Vista de diferencias entre puntos de restauración.
- ~~Transiciones animadas configurables entre láminas~~ → **hecho** (§3.18a).
- ~~Reordenar arrastrando~~, ~~historial global~~, ~~service worker~~ → **hechos**
  (§3.14, §3.18).
- ~~Pizarra infinita / herramienta separada para proyectos~~ → **hecho**:
  «Presentar un proyecto» (§3.19).
- ~~Exportar la pizarra a PDF y PPTX~~ → **hecho** (§3.19): cada marco numerado
  se exporta como página / diapositiva, rasterizando el modelo vectorial propio
  de la pizarra en vez de `html2canvas`.
- ~~Enlaces cortos para proyectos de pizarra~~ → **hecho** (§3.19, `js/wb-cloud.js`,
  tabla D1 `boards`): 874 → 118 caracteres (−86,5 %), republicar sin duplicar,
  abrir por código desde el menú.

---

## 9. Próximos pasos recomendados

1. **Hosted Deploy** para activar la nube (enlaces cortos, comentarios) y el modo
   sala (`sw.js` necesita HTTPS).
2. **Analítica de lectura**: la columna `views` ya se incrementa; falta mostrar por
   deck cuántas veces se abrió y desde cuándo.
3. **Control de acceso por identidad** en las rutas publicadas, si el material
   confidencial llega a compartirse por enlace corto.
4. **Analítica de lectura para la pizarra**: la columna `views` de `boards` ya se
   incrementa; falta mostrarla en «Mis proyectos», igual que se sugiere para `decks`.

---

## 10. Publicación

Sitio estático sin build: basta con servir la carpeta.

**Importante para la nube (§3.11):** los enlaces cortos y los comentarios necesitan
la base de datos, que **solo existe en un Hosted Deploy**. Al desplegar, el esquema
de `.tables/schema.json` provisiona la base D1 en Cloudflare. Antes de ese despliegue,
el panel de Nube funciona pero avisa de que no hay base de datos disponible, y el
enlace largo (`#d=`) sigue operando con normalidad porque no necesita servidor.

Los cambios en el repositorio **no** llegan al dominio en vivo hasta que se realiza
una publicación o un Hosted Deploy aprobado.

**«Presentar un proyecto» funciona igual sin Hosted Deploy.** `proyecto.html` no
*necesita* la base de datos: todo vive en `localStorage` y en el fragmento `#p=`
del enlace, igual que el enlace largo `#d=` del deck, y eso ya funciona en la
vista previa, en una publicación simple o en un Hosted Deploy. Su **enlace corto**
(`#w=`, tabla `boards`) es la única parte de la pizarra que sí requiere el Hosted
Deploy con base de datos, con la misma condición que la nube del deck (§3.11):
antes de desplegar, el botón «Publicar» del modal Compartir avisa con claridad
en vez de fallar en silencio.
