Deck privado
/deck es el pitch para inversionistas y lectores invitados. No está en la navegación pública. Antes de la primera diapositiva la página pide un correo (nombre y organización son opcionales). Sin ese paso no hay diapositivas: las imágenes no viven en public/.
La página lleva noindex (meta y cabecera X-Robots-Tag) y robots.txt desautoriza /deck.
Qué ve quien entra
Una diapositiva a la vez, con contador (3 / 11), botones anterior y siguiente, flechas del teclado y deslizamiento en el teléfono. El idioma no sigue el del sitio ni el del navegador. Arriba a la derecha hay un selector EN | ES. ?lang=es o ?lang=en gana; si no viene en la URL, se usa la última elección guardada en el navegador; si no hay ninguna, el deck abre en inglés. El mismo selector traduce la puerta (correo, nombre, organización). La sesión queda en una cookie HttpOnly (futuros_deck) por 14 días, firmada con HMAC, así que no hay que volver a escribir el correo en cada diapositiva.
Dónde quedan los correos
Cada envío válido se agrega a la lista Redis deck:viewers:v1 (Upstash, las mismas variables UPSTASH_REDIS_REST_URL y UPSTASH_REDIS_REST_TOKEN del libro de la confianza). Cada fila guarda correo, nombre y organización si los hubo, fecha, user-agent y referrer.
Para leerlos:
- Consola de Upstash, comando
LRANGE deck:viewers:v1 0 -1(lo más nuevo queda al final). - O
GET /api/deck-accessconAuthorization: Bearer <DECK_GATE_SECRET>. Sin ese secreto el listado no se abre; la cookie de un visitante no lista a los demás. - Si Mailgun ya está configurado (
MAILGUN_API_KEY,MAILGUN_DOMAIN,MAILGUN_FROM), también sale un correo aDECK_NOTIFY_EMAILo, si no está puesta, amarcus@futuros.xyz.
Si Redis no está configurado, la respuesta dice persisted: false. La cookie igual se emite (para no dejar a alguien frente a una sala vacía) y el evento de PostHog sigue llevando el correo como propiedad de persona.
Analítica
En el dominio de producción, PostHog recibe deck_gate_view, deck_email_submit, deck_slide_view (con slide) y deck_complete. Cada uno lleva lang (en o es). Cambiar el selector dispara deck_lang_switch con from, to y slide. El evento del navegador marca via: "client". El servidor manda otro deck_email_submit con via: "api" para que un bloqueador no borre el envío. El aviso por correo incluye el idioma como campo Language. El distinct_id es el mismo pseudónimo sha-256 que el resto de la plataforma (c_ más 24 hex), no el correo en claro. El correo sí queda como propiedad de la persona, igual que en la puerta de /contribuir.
Variables
| Variable | Para qué |
|---|---|
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN | Guardar la lista deck:viewers:v1. Si no hay DECK_GATE_SECRET, el token también firma la cookie. |
DECK_GATE_SECRET | Firma la cookie y autoriza el listado. 16 caracteres o más. |
DECK_NOTIFY_EMAIL | Destino del aviso. Por defecto marcus@futuros.xyz cuando Mailgun ya está activo. |
MAILGUN_* | Las mismas del resto del producto. Sin ellas no hay correo; la lista en Redis sigue siendo el registro. |
Las diapositivas
Hay dos idiomas de diapositivas, las mismas once láminas. El inglés vive en deck-slides/01.webp … 11.webp y es el que abre por defecto. El español vive en deck-slides/es/. Un enlace directo al español es futuros.xyz/deck?lang=es. /api/deck-slide?n=3&lang=es entrega el archivo español y exige la misma cookie. Cualquier otro valor de lang vuelve al inglés. Cache-Control: private, no-store para que la CDN no lo guarde sin sesión; la clave de caché es la URL completa, incluido lang.
El PDF inglés no se publica. Va en deck-src/futuros-deck.pdf (imágenes aplanadas, sin capa de texto). scripts/render-deck-slides.sh lo rasteriza a los WebP ingleses (288 dpi, 4× sobre la grilla de puntos, 4320×2430, calidad WebP 100). Esos archivos no se regeneran desde HTML.
La fuente editable del deck en español —y de una reconstrucción en inglés para comparar— es deck-src/html/ (strings.json con los dos idiomas, deck.css). Para volver a rasterizar el español:
bun scripts/render-deck-html.ts esEso escribe deck-slides/es/01.webp … 11.webp a 4320×2430. bun scripts/render-deck-html.ts en --out <directorio> escribe una prueba en inglés solo en ese directorio, nunca encima de deck-slides/.
Esos WebP entran en el bundle de la función (includeFiles: deck-slides/** en vercel.json), no en public/. Si la carpeta está vacía, la puerta igual guarda el correo y la página dice que no pudo abrir el deck. GET /api/deck-access incluye languages: ["en","es"].