About the project - NerdTalk
Inspiración
Los eventos virtuales suelen romperse por tres motivos que no tienen nada que ver con las charlas: la fricción para entrar (cuentas, registros, instalaciones), oradores que descubren sus problemas técnicos recién cuando ya están en vivo, y organizadores que no ven qué pasa en cada sala mientras pasa.
NerdTalk nació de querer resolver eso para los eventos virtuales de Nerdearla, una comunidad que vive de que la gente pueda entrar, escuchar y preguntar sin trámites. La idea de partida fue simple de enunciar y difícil de cumplir:
Nadie debería crear una cuenta para ver o dar una charla, y quien organiza tendría que ver todo en tiempo real.
De ahí salió el diseño: solo el administrador tiene cuenta; los asistentes entran con un link (y una contraseña si el evento es privado); los oradores entran con un link de invitación personal y, una vez adentro, tienen un rol real dentro de la app: pueden dar roles a otras personas de su propia sesión.
Qué aprendimos
1. El orador no es un usuario, es un permiso con alcance. Modelarlo como cuenta hubiera roto el "sin registro". Modelarlo como un permiso concedido por sesión, con un link personal, mantuvo la simplicidad y resolvió de paso la seguridad: el link solo abre su sesión.
2. Delegar es un problema de jerarquía. Si un orador puede dar roles, hay que evitar que se escale a sí mismo. Definimos un orden de rangos
$$ \text{ATTENDEE} < \text{GUEST} < \text{COSPEAKER} < \text{MODERATOR} < \text{SPEAKER} < \text{ADMIN} $$
y una regla única: quien otorga un rol de rango $r_g$ debe tener un rango $r_a$ tal que $r_a > r_g$ (salvo el admin). Para quitar un rol vale lo mismo, más una excepción: siempre se puede quitar lo que uno mismo otorgó. Al revocar a alguien se revocan en cascada los roles que esa persona había dado.
3. Un código de reunión no es un secreto.
El código abc-defg-hij sirve para compartir, no para autorizar. Con un alfabeto de $23$ letras (sin i, l, o, para no confundirlas al dictarlas) y 10 posiciones hay
$$ N = 23^{10} \approx 4{,}1 \times 10^{13} $$
combinaciones. Con $n$ sesiones ya creadas, la probabilidad de que un código nuevo choque es aproximadamente $n/N$; para $n = 10^4$ da $\approx 2{,}4 \times 10^{-10}$. Es tan baja que alcanza con una restricción UNIQUE en la base y reintentar. En cambio, el token de invitación de un orador sí es un secreto: son $256$ bits aleatorios, $2^{256}$ posibilidades, y en la base guardamos solo su hash.
4. El tiempo lo tiene que decidir el servidor. Si cada cliente calcula su propio reloj, el orador, el moderador y el panel de control ven cosas distintas. El servidor calcula, a partir de timestamps:
$$ \text{restante}(t) = \max\bigl(0,\; 60\,d + e - (t - t_0)\bigr) $$
donde $d$ es la duración en minutos, $e$ la extensión en segundos que puede sumar el admin y $t_0$ el instante en que la sesión pasó a estar en vivo. El desvío contra la agenda es $t_0 - t_{\text{prog}}$. Los avisos ("5 min", "1 min", "cerrá ya") se disparan cuando $\text{restante}$ cruza $300$, $60$ y $0$ segundos.
5. Los permisos viajan firmados. El servidor de audio y video nunca decide quién puede publicar: recibe permisos en un token que firma NerdTalk, con vencimiento corto y limitado a una sala. Cuando alguien cambia de rol con la sala abierta (por ejemplo, un asistente que sube al escenario), se actualizan sus permisos sin reconectarlo.
Cómo lo construimos
Hasta ahora el trabajo fue diseñar el producto con rigor y dejar el terreno preparado. Todavía no hay una transmisión funcionando.
Documentación como fuente de verdad. Armamos un vault de Obsidian en docs/ con la visión, los roles y permisos, los eventos públicos y privados, el panel de control del administrador, el sistema de oradores completo, la lógica de las meetings y el roadmap por fases. Cada decisión que asumimos sin confirmar quedó registrada en una nota de decisiones abiertas.
Diseño de las piezas principales.
- Eventos, escenarios y sesiones: un evento tiene escenarios y cada escenario tiene sesiones; solo una puede estar en vivo a la vez, garantizado con un índice único parcial en PostgreSQL.
- Eventos privados: llevan contraseña (guardada con hash) y no aparecen en la landing, en listados ni en el sitemap.
- Máquina de estados de cada sesión:
SCHEDULED → BACKSTAGE → LIVE → ENDED, con transiciones validadas en una transacción. - Sistema de oradores: invitación por link, portal con checklist, prueba técnica en el navegador, backstage, control de tiempo, preguntas moderadas y delegación de roles.
- Panel de control con estado, tiempo, integrantes y latencia por escenario, con semáforo.
Stack. Next.js 16 (App Router) con React 19 y Tailwind v4, TypeScript, PostgreSQL 17 con Prisma 7, Redis 7 para presencia, cues y pub/sub, todo levantado con Docker Compose. Para el audio y video en vivo propusimos un SFU de WebRTC open source y self-hosteable (LiveKit), decisión que todavía tenemos que confirmar. La identidad visual toma la paleta y el tono de Nerdearla, para que NerdTalk se lea como una sub-marca.
Desafíos
- Cero fricción sin perder control. Sin cuentas, cada credencial es un link. Hubo que pensar qué pasa si se filtra: se enlaza a un dispositivo, se puede revocar y el token se guarda solo como hash.
- Delegar sin abrir una puerta de escalada. Que el orador pueda dar roles es útil y peligroso a la vez. Las reglas de rango, alcance por sesión, cascada, vencimiento y auditoría existen para cerrar esa puerta.
- Casos borde en vivo. Un orador que se cae, uno que no aparece, una sesión que se atrasa, un reemplazo de último momento. Cada uno tiene un comportamiento definido antes de escribir código.
- Escalar a una audiencia grande. WebRTC escala mal a miles de espectadores; dejamos previsto medirlo temprano y tener una salida por HLS.
- Un Next.js con cambios. El repo usa una versión de Next.js que difiere de lo habitual, así que el código de referencia de la documentación hay que verificarlo contra las guías incluidas en
node_modulesantes de implementarlo. - Decidir qué no hacer. Dejamos afuera cuentas de asistentes, pagos, CFP y networking 1:1 para mantener el foco en lo que hace fallar a un evento virtual.
Built With
- next.js
- postgresql
- prisma
- react
- redis
- tailwind
- typescript
Log in or sign up for Devpost to join the conversation.