Una app para ordenar la cursada, entera en el teléfono
My School es una app mobile de organización académica para estudiantes: cargás tus materias, lo que hay que entregar en cada una y las tareas que componen cada entrega, y la app te muestra qué vence y cuándo. La estructura es una jerarquía de tres niveles —Materia → Actividad (con Proyecto opcional que agrupa) → Tarea— más una vista de calendario que junta todo lo que tiene fecha.
Está construida con Expo 54 y Expo Router 6 sobre React Native 0.81 y React 19, en TypeScript 5.9, con
Zustand para el estado y un sistema de theming propio con soporte claro/oscuro e i18n español/inglés.
Corre también en el navegador vía react-native-web, que es de donde salen las capturas de esta página.
Funciona 100% local: no hay backend, ni cuentas, ni sincronización entre dispositivos. Es un dato del alcance, no una promesa pendiente — todo lo que la app guarda vive en el teléfono.
Es un proyecto 100% propio: 32 commits, todos míos, sin fork ni upstream que acreditar, en una ventana de 13 días (2026-04-22 a 2026-05-05).
26 wireframes HTML antes de escribir una línea de React Native
Esta es la parte que más define al proyecto, y se puede verificar sin creerme nada: está en el orden de
los commits. Los primeros seis commits del repo no tienen una línea de React Native. Son la carpeta
design/: 26 wireframes HTML (design/views/*.html con su design/css/wireframe.css), escritos a
mano, uno por pantalla de la app. Recién después aparece
chore: initialize Expo 54 project setup (Fase 0).
De ahí en adelante el trabajo va por fases numeradas, y cada fase implementa una parte de lo que ya estaba dibujado:
| Fase | Qué entra |
|---|---|
| Fase 0 | Setup del proyecto Expo 54 |
| Fase 1 | Tokens y sistema de theming |
| Fase 2 | Capa de datos |
| Fase 3 | Navegación |
| Fase 4 | Materias (Course) |
| Fases 5-7 | Tareas, Actividades y Proyectos |
| Fase 8 | Calendario |
| Fase 9 | Búsqueda |
| Fase 10 | Settings |
| Fase 11 | Polish |
Después de la Fase 11 vienen los commits de fixes y pulido, y la internacionalización entra al final, en su propio commit dedicado.
Lo que este orden compra es concreto: la Fase 1 puede arrancar por los tokens de diseño porque las 26 pantallas ya existían en HTML y se sabía qué colores, tipografías y espaciados hacían falta. La Fase 3 puede armar la navegación porque el mapa de pantallas ya estaba completo, no se iba descubriendo. Y las fases 4 a 7 son verticales de producto —una entidad entera cada una— en vez de “hacer todas las pantallas a medias y volver”.
El costo también es real: diseñar 26 pantallas antes de programar es trabajo que no produce app durante seis commits, y compromete decisiones de UI antes de haber tocado un dispositivo. En este proyecto salió bien porque el alcance estaba acotado y era para un usuario conocido; no es una receta que se pueda mudar tal cual a un producto con requisitos en movimiento.
Tres niveles, y un cuarto opcional que agrupa
El modelo es lo que hace que la app sirva para una cursada real y no sea una lista de tareas con un
nombre bonito. Está documentado en docs/architecture.md y declarado en src/types/entities.ts:
Course (Materia)
└── Project (opcional — agrupa activities relacionadas)
└── Activity
└── Task
└── Activity (suelta, sin project)
└── Task
└── Task (suelta, sin activity)La clave está en los “opcional” y “suelta”: ningún nivel intermedio es obligatorio. Una tarea puede colgar directo de la materia sin inventarle una actividad contenedora, y una actividad puede existir sin proyecto. Un modelo rígido de cuatro niveles obligatorios te fuerza a crear entidades vacías solo para llegar al nivel donde querés anotar algo — que es exactamente el momento en el que alguien deja de usar la app.
Esa flexibilidad obliga a definir reglas de borrado que no son las de un árbol común, y están escritas:
Borrar una materia borra en cascada
El Course es el único nivel que arrastra todo lo que cuelga de él: si la materia se va, se van sus proyectos, actividades y tareas.
Borrar un proyecto o una actividad NO borra a sus hijos
Los hijos quedan sueltos bajo el Course, que es el nivel que sí existe siempre. Reorganizar no puede costarte datos.
El progreso se calcula, no se carga
El avance de un Project es tasks completadas sobre tasks totales — un número derivado, sin campo que pueda quedar desactualizado.
Al calendario solo llega lo que tiene dueDate
Sin fecha de vencimiento, un ítem existe en su materia pero no ocupa lugar en el calendario.

Materias, actividades y calendario, funcionando
Las capturas son de la app real corriendo en navegador con los datos de prueba que genera el propio botón de Settings. Son las tres vistas principales, una por pestaña.



La pantalla de actividades es donde se ve la jerarquía de la zona anterior puesta a trabajar: las sub-pestañas Tasks / Actividades / Projects son los tres niveles, y el filtro por materia los cruza con el nivel de arriba. Cada tarea muestra a qué materia y a qué actividad pertenece, así que la lista plana no pierde el contexto que da el árbol.
Una capa de repositorios sobre un storage que cambia según la plataforma
El acceso a datos no está desparramado por los componentes: hay una capa de repositorios con
interfaces en src/repositories/. Una interfaz genérica define el contrato y cada entidad tiene la
suya, separada de su implementación concreta:
src/repositories/
IRepository.ts ← genérica: getAll / getById / create / update / delete
ICourseRepository.ts IActivityRepository.ts
IProjectRepository.ts ITaskRepository.ts
courseRepository.ts activityRepository.ts ← implementaciones
projectRepository.ts taskRepository.tsDebajo hay una sola implementación, y ahí está el detalle que importa: el almacenamiento cambia según
la plataforma. src/storage/storageAdapter.ts elige por Platform.OS entre expo-secure-store en
iOS y Android y localStorage en web, así que la misma app corre en el teléfono y en el navegador sin
que el resto del código se entere. Es lo que hace posible que estas capturas existan.
Lo que hay que decir con la misma claridad: no hay una base de datos real. No hay SQLite ni ningún motor detrás — es persistencia de estado (Zustand con su middleware de persistencia, apoyado en ese adapter), no una capa de datos. Funciona para el alcance de la app y no pretende más que eso.
El resto sigue el mismo corte: app/ —las rutas de Expo Router, con su grupo (tabs) y sus rutas
dinámicas [id].tsx— queda delgado, y la lógica vive en src/ repartida en components/, stores/,
repositories/, theme/, hooks/, i18n/ y utils/.
13 días, sin tests y sin CI, con un pendiente a la vista
No hay tests y no hay CI
Cero archivos de test en el repo y ningún workflow de GitHub Actions: no existe .github/. Lo digo derecho porque es verdad y porque no es de lo que trata este caso: acá lo que se demuestra es el proceso de diseño→código y el producto que salió de él, no una disciplina de testing que este proyecto no tuvo. En 13 días entró la app entera y quedó afuera todo lo automatizado.
El TODO.md sigue con un ítem abierto
Queda un pendiente anotado: mostrar en la vista semana del calendario los proyectos que tienen dueDate. Lo dejo escrito en vez de borrarlo, y aun así el proyecto figura como finalizado: el MVP está cerrado y usable, y ese detalle no bloquea nada de lo que la app hace hoy.
Hay una imprecisión heredada que corrijo acá: la descripción del repo en GitHub dice “React Navigation”,
y la app en realidad usa Expo Router — React Navigation aparece como dependencia transitiva, no como
la API que se programa. También hay un docs/project.md de arranque que quedó desactualizado (todavía
lista como “a confirmar” cosas que el código resolvió hace 32 commits); lo dejo como lo que es, evidencia
de que hubo planificación previa, no como fuente del stack.
El proyecto está finalizado en el sentido más literal: 32 commits propios, 13 días, una app completa con su jerarquía de datos, su calendario, su búsqueda, sus dos idiomas y sus dos temas. Lo que me llevo es la parte del principio: dibujar las 26 pantallas antes de escribir código es lo que hizo que las 11 fases fueran implementación y no descubrimiento.