TIC Platform
El problema
En una carrera técnica de nivel secundario, entre 100 y 120 alumnos por año tienen que proponer una idea de proyecto, discutirla y terminar formando equipos de trabajo. Este cuatrimestre corren dos años en paralelo. Salen entre 25 y 35 proyectos por año. La herramienta que usaban para eso era Padlet: un muro de post-its sin roles ni permisos. Cualquiera podía ver cualquier idea apenas se publicaba, no había forma de aprobar o rechazar antes de mostrarla, y armar los equipos significaba cruzar a mano quién se había anotado en qué idea.
El ciclo de ideación y armado de equipos dura dos semanas. Eso es una restricción de calendario de la materia, no de la herramienta — no la inventé yo, y cualquier solución que se use la hereda igual.
Qué construí, y en qué orden
La primera pieza fue un tablero de ideas moderado: el alumno publica una tarjeta, queda en pending, invisible para el resto de los alumnos hasta que un docente la mueve a approved. El docente puede además mandarla a waiting — ni rechazada ni publicada, un estado intermedio para "todavía no" — o borrarla con soft-delete. Un alumno puede leer su propia tarjeta pendiente pero no editarla una vez enviada, y el autor de una tarjeta no lo puede cambiar ni un moderador.
Encima de eso agregué postulaciones: un alumno se anota a una idea publicada con un rol (frontend, backend, diseño...), no solo con un like. Y encima de las postulaciones, una pantalla aparte — /boards/[id]/grupos — donde el docente cruza postulaciones contra grupos definitivos: quién quedó en qué idea, con qué rol, y si hace falta partir una idea en dos equipos porque se anotó más gente de la que un solo grupo puede absorber (fork_subgroup_to_card, un caso que apareció en producción y que no estaba en el diseño original). Cerrar un grupo tiene dos niveles. Un estado "marcado" que es puramente visual, y un "bloqueo duro" que sí impide en la base de datos agregar o sacar alumnos. El primer nivel se usó de intermedio antes de animarse al segundo — necesitaba los dos porque el docente quería poder señalizar sin comprometerse todavía.
Con la primera cursada corriendo en producción llegó la señal más fuerte: los grupos quedaban armados, pero no había manera de ver cómo iba cada proyecto. Ese hueco se convirtió en el segundo módulo — un tablero de tareas estilo kanban, uno por grupo, con drag & drop, plantillas de tareas por tipo de proyecto, dependencias entre tareas y vistas de calendario y timeline. Se activa por tablero con un toggle, y por grupo solo cuando ese grupo ya tiene el bloqueo duro — el kanban no existe si el armado de equipos sigue abierto.
Ahí la herramienta dejó de ser un reemplazo de Padlet y pasó a ser una plataforma con tres módulos: ideación, tareas, y un tercero — cierre de proyecto — que todavía es una pantalla placeholder ("Próximamente"). Reconocer eso en el código, en vez de simularlo, también es una decisión de producto: no hay nada peor que un botón que promete algo que no existe.

Build vs. buy
Padlet no alcanzaba porque no tiene roles. No hay forma de distinguir "esto lo ve un alumno" de "esto lo ve un docente", y sin esa distinción no se puede moderar antes de publicar ni impedir que un alumno edite una idea ajena.
Evalué Notion antes de escribir la primera línea de código. Se cae por dos razones. El plan gratuito tiene límites que la carga real de la materia supera. Y pagar la plataforma completa era resolver con una suscripción algo que igual quedaba fuera de lo que Notion sabe hacer bien: moderar con estados y condicionar la visibilidad por rol pide un modelo de permisos, y lo que Notion ofrece es una base de datos con vistas.
Las tres decisiones que dije que no
Matching automático de equipos.
No hay ningún algoritmo que arme grupos. La preselección es manual: el docente arrastra postulantes de una lista a un grupo (assignDirectly, preselectApplicant). El trade-off es tiempo del docente contra criterio del docente. A esa edad, quién trabaja bien con quién lo sabe el que los ve todas las semanas, y no hay campo de formulario que lo capture.
Postulaciones visibles en vivo, por default no.
El conteo de postulantes es siempre visible, pero quién se postuló a qué — con nombre y rol — está detrás de un toggle por tablero que el docente activa si quiere (show_applicants, apagado por default). El trade-off es presión de grupo: ver en vivo que "todos se anotaron a la idea popular" empuja la decisión del alumno más que la idea en sí.
IA para redactar ideas.
La descarté por output sintético — un generador de texto llena el campo, pero la idea que hay detrás sigue vacía. El objetivo de esas dos semanas es que el alumno piense un proyecto, no que produzca un párrafo que lo parezca.
Qué pidió el uso real que no estaba previsto
El kanban entero. No estaba en el plan original — el documento de requerimientos de ese módulo se escribió recién en agosto, después de que el ciclo de ideación ya hubiera corrido entero sin él. Un docente con 30 proyectos en simultáneo no tenía cómo saber cuáles estaban trabados sin ir a preguntarle a cada grupo, uno por uno. Por eso la vista de docente muestra todos los grupos del tablero juntos, con indicadores de tareas vencidas y de responsable sin asignar.
La otra cosa que pidió el uso real fue rendimiento. Con el tablero en uso, la base empezó a acusar la carga: rehice las políticas de RLS y consolidé consultas sueltas en un par de RPCs (get_board_header, get_card_meta). Lo apliqué en producción tabla por tabla, con lock_timeout, para no trabar el tráfico que estaba corriendo en ese momento. Nada de eso estaba planificado; lo empujó la carga de una cursada en curso.
Un pedido más chico: exportar a CSV la preselección de grupos. El docente lo necesitaba para llevar la planilla a otra reunión, fuera de la plataforma — no todo se resuelve manteniendo a la gente adentro de la herramienta.

Lo que no funcionó
Los comentarios en las ideas — la sección de feedback abierta bajo cada tarjeta — no tuvo tracción. El debate se daba en persona, no ahí. Los dejé: sacarlos costaba una migración y una decisión de producto sobre qué hacer con el historial ya escrito, y no había ninguna razón activa para pagar ese costo. Siguen ahí, sin uso. Dejarlos costó menos que sacarlos, y eso también es una decisión.
Deuda técnica reconocida
Los módulos de postulaciones y armado de grupos son los de más uso del sistema, y son los que peor cobertura de tests tienen. applications.ts — postularse, retirar la postulación, ver el grupo asignado — no tiene ningún test. Lo que sí tiene tests es la mitad "docente" de armado de grupos: la preselección, el fork de subgrupos, las notas de equipo. Es la parte más nueva y más compleja del sistema (dnd-kit con soporte táctil, tres estados de bloqueo, cruces de RLS con card_group_closures) corriendo sobre la parte más vieja y menos verificada.
Cómo lo construí
Escribí la plataforma con Claude Code. Cerca del 70% de mis commits llevan a Claude como coautor, y está a la vista en el historial de git.
Qué construir y qué descartar lo decidí yo. Las tres decisiones de arriba salieron de mirar a un docente armar grupos a mano, no de la sugerencia de un modelo. Y hay una parte que revisé línea por línea: las políticas de RLS. Un error de permisos no se ve en la interfaz. Se ve cuando un alumno lee algo que no le corresponde.
Stack
Next.js 16 (App Router) sobre React 19. Supabase para Postgres, Auth y Realtime — toda la lógica de permisos vive en RLS, no en el frontend. Vitest para tests unitarios de server actions y helpers de fecha/scheduling del kanban. @dnd-kit para el drag & drop, con soporte táctil real. Deploy en Vercel, con CI en GitHub Actions y versionado semántico automático a partir de Conventional Commits.