Portafolio profesional
Construyendo un portafolio de doble personalidad con React 19 y arquitectura screaming
Cómo implementé un portafolio web que presenta dos identidades artísticas distintas usando React 19, GSAP y una arquitectura modular que separa claramente los modos 'Día' y 'Noche'.
Artículo
# Arquitectura de Modo Dual: Un Portafolio Web que Encarna Dos Personas
## El problema
Construir un portafolio web para una artista profesional que esencialmente tiene dos carreras distintas. Por un lado, Diana Paola Agudelo, una bailarina clásica con una trayectoria profesional pulida. Por otro, JASH.D, su alter ego artístico experimental que trabaja con motion graphics y arte digital. El desafío no era solo mostrar dos conjuntos de trabajo diferentes, sino crear una experiencia web que *encarnara* la esencia de cada persona. La solución típica - dos páginas separadas o una sola con secciones - no capturaba la transformación psicológica que ocurre cuando la artista cambia entre sus modos creativos.
## Por qué React 19 + GSAP + Arquitectura Gritona
Podría haber usado un CMS tradicional o incluso un generador de sitios estáticos, pero necesitaba un control granular sobre cada micro-interacción y transición. React 19 me dio acceso a las nuevas APIs de transiciones y efectos concurrentes que son cruciales para animaciones fluidas. GSAP era la única librería que podía manejar la complejidad de las secuencias de animación coordinadas entre scroll, timeline y eventos de UI. Y elegí una "arquitectura gritona" (screaming architecture) porque el dominio del problema es claramente dual - el sistema debe gritar "¡hay dos modos distintos aquí!" desde la estructura de carpetas. No es solo una cuestión de estilos CSS; es una separación conceptual que debe reflejarse en el código.
## Arquitectura
```mermaid
flowchart LR
Entry["index.html
Punto de entrada"] -- mounts --> Root["src/main.tsx
React root"]
Root -- renders --> App["src/App.tsx
Router & ModeProvider"]
App -- conditionally renders --> DayMode["src/day/
Modo Día components"]
App -- conditionally renders --> NightMode["src/night/
Modo Noche components"]
DayMode -- uses --> SharedUI["src/components/
UI compartida (shadcn)"]
NightMode -- uses --> SharedUI
SharedUI -- styles with --> Tailwind["Tailwind CSS
Sistema de diseño"]
DayMode -- animates via --> GSAP["GSAP
Motor de animación"]
NightMode -- animates via --> GSAP
GSAP -- syncs with --> Lenis["Lenis
Scroll suave"]
NOTE["Evidence gap:
No hay archivos de routing
visibles en la estructura"]
```
Como ves en el diagrama, el flujo comienza con el punto de entrada estándar de Vite que monta la aplicación React. El componente `App` actúa como orquestador principal, manejando el proveedor de contexto para el modo actual (Día/Noche) y renderizando condicionalmente la jerarquía de componentes correspondiente.
**Decisiones arquitectónicas clave:**
1. **Separación física por modo en `src/day/` y `src/night/`**: En lugar de usar props o flags para alternar contenido, creé dos estructuras de carpetas paralelas. Esto hace que la dualidad sea explícita en el sistema de archivos. Si abrís el proyecto, inmediatamente entendés que hay dos "aplicaciones" dentro de una.
2. **UI compartida pero temática (`shadcn/ui` + Tailwind)**: Usé `shadcn/ui` como base de componentes accesibles, pero cada modo aplica sus propias variantes de tema a través de clases de Tailwind. Esto me permitió mantener consistencia en interacciones y accesibilidad mientras permitía paletas de colores y estilos radicalmente diferentes.
3. **Orquestación centralizada de animaciones con GSAP Context**: En lugar de tener timelines de GSAP dispersas por componentes, creé un sistema central que registra todas las animaciones y las coordina. Esto fue crucial para evitar conflictos cuando el usuario cambia de modo a mitad de una animación.
## El trade-off que nadie te cuenta
El trade-off más doloroso fue entre **performance de bundle size y expresividad artística**. GSAP es una librería enorme (~40KB minified), y cuando la combinás con React 19, `shadcn/ui`, y todas las utilidades de Tailwind, el bundle inicial se infla considerablemente. Intenté code-splitting agresivo, pero las animaciones de entrada dependen tanto de GSAP que cualquier retraso en la carga se nota como "jankiness" visual. La solución fue un loading screen artístico que oculta el problema - pero técnicamente, estoy cargando mucho JavaScript antes de que el usuario pueda interactuar. Acepté este trade-off porque la fluidez de las animaciones era no negociable para la experiencia inmersiva.
## Resultado
- **Transición de modo seamless**: Cambiar entre Diana y JASH.D toma menos de 800ms e incluye una secuencia de animación coordinada que transforma colores, tipografía y layout simultáneamente.
- **Scroll a 60fps constante**: Gracias a la integración de Lenis con GSAP ScrollTrigger, incluso en dispositivos móviles de gama media se mantiene fluidez perfecta.
- **Dos experiencias visualmente distintas con código compartido**: El 70% de los componentes lógicos (hooks, utilidades, config) se reutiliza entre modos, mientras que el 100% de la expresión visual es única para cada persona.
- **Arquitectura que escala para futuros modos**: La estructura gritona permite agregar un tercer modo (por ejemplo, un "Modo Estudio") sin refactorizar los existentes.
## Lo que haría diferente
Usaría React Server Components desde el inicio para el contenido estático de cada modo. Implementé ISG (Incremental Static Regeneration) manualmente, pero RSC me hubiera dado una separación más clara entre lo que es dinámico (las interacciones, animaciones) y lo que es estático (la biografía, lista de trabajos). También exploraría más alternativas a GSAP para animaciones más simples, quizás con Framer Motion para ciertos componentes, aunque perdería la potencia de la línea de tiempo maestra de GSAP.
¿Querés profundizar en algún componente? Contactame o revisá el código en https://github.com/Albarracin-sg/PORTAFOLIO-JASH.D.