🖼️ 3.6 · UI/UX avanzado: Atomic Design y design systems en React

⏱ 5h 00min ⚡ 100 XP 🏅 Mid Junior 📖 Frontend Moderno y Proyecto Final
"Un componente no es un componente: es un átomo, molécula, organismo o página."

🎯 Objetivo del tema

Al terminar este tema serás capaz de: Al terminar este tema serás capaz de diseñar sistemas de componentes escalables usando Atomic Design: átomos, moléculas, organismos, templates, y páginas. Aprenderás a crear tu propio design system con tokens, variantes, y documentación. La meta: que cualquier persona en tu equipo pueda construir pantallas coherentes sin preguntarte 'qué clase uso para el botón grande'.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

🗺️ Mapa del tema

Cuando tu app crece de 5 componentes a 50, surge un problema: el Button de la pantalla de login se ve diferente al de la pantalla de perfil, y nadie sabe por qué. El Atomic Design (de Brad Frost) resuelve esto con una jerarquía clara: átomos (Button, Input, Label) → moléculas (SearchBar = Input + Button) → organismos (Header con logo + nav + search) → templates (layout principal) → páginas (instancias concretas). Es la metodología que usan Linear, Notion, Figma, y los designers profesionales. Hoy la aplicas a tu app de Atlas Mundial.

1. Atomic Design: la metodología que escaló React

De átomos a páginas: la pirámide de complejidad.

2. Átomos: Button, Input, Label, Badge, Spinner

Los componentes más pequeños, sin estado propio.

3. Moléculas: SearchBar, FormField, Card

Combinaciones de átomos que forman unidades funcionales.

4. Organismos: Header, UserCard, ProductList

Secciones completas con su propia lógica y estado.

5. Templates y páginas: layout + instancias

La estructura reutilizable y las páginas concretas.

6. Documentación de componentes con Storybook

Tu catálogo vivo de componentes: cómo se ven todas las variantes.

3.6.1 Atomic Design: la metodología que escaló React

Brad Frost publicó Atomic Design en 2013 inspirado en la química: así como la materia se compone de átomos, las interfaces se componen de 5 niveles de componentes. Es la metodología estándar en design systems profesionales: Linear, Notion, Figma, IBM, Salesforce. Te da un vocabulario común y una jerarquía clara que escala de 5 a 5000 componentes sin perder consistencia.

📷 Imagen referencial: Pirámide de Atomic Design: en la base los átomos (Button, Input), luego moléculas (SearchBar), organismos (Header), templates (layout), y en la cima las páginas (instancias concretas).
NivelQué esEjemplos en Atlas MundialEstado propio
ÁtomosComponentes más pequeños, sin estado.Button, Input, Label, Badge, Spinner, Avatar.No.
MoléculasCombinación de átomos que forman una unidad.SearchBar (Input + Button), FormField (Label + Input + error).A veces (input del usuario).
OrganismosSección completa con su propia lógica.Header (logo + nav + search), PaisCard (imagen + textos + acciones).Sí (datos del API, hover state).
TemplatesLayout reutilizable sin contenido.Layout principal (Header + main + Footer).No (recibe children).
PáginasInstancia concreta con datos reales.Página de Argentina (Header + PaisCard + Clima + Mapa).Sí (datos específicos).
⭐ Atomic Design: empieza cuando tienes 20+ componentes: Atomic Design no es solo teoría: es la base de Storybook (que verás en el bloque 3.6.6), de design systems profesionales (Material UI, Chakra, shadcn/ui), y de cómo los equipos grandes (IBM, Salesforce) mantienen consistencia en 1000+ componentes. La regla: cuando tu app tenga más de 20 componentes, EMPIEZA a usar Atomic Design. Antes, es overkill. Después, es esencial.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.6.2 Átomos: Button, Input, Label, Badge, Spinner

Los átomos son los componentes MÁS PEQUEÑOS de tu app. No tienen estado propio (no son useState), no hacen fetch, no tienen lógica. Son BLOQUES VISUALES puros: Button, Input, Label, Badge, Spinner, Avatar, Icon. Son la base de todo lo demás, así que deben estar bien hechos y ser REUTILIZABLES. Un Button con 10 props para 10 variantes es un átomo bien diseñado.

El átomo Button con 4 variantes y 3 tamaños

// atoms/Button.jsx: el átomo más importante de tu app
import './Button.css';

// Props del Button: variant, size, disabled, loading, onClick, children
export function Button({
  variant = 'primary',     // primary | secondary | danger | ghost
  size = 'md',           // sm | md | lg
  disabled = false,
  loading = false,
  onClick,
  type = 'button',
  children,
  ...rest
}) {
  return (
    <button
      type={type}
      onClick={onClick}
      disabled={disabled || loading}
      className={`btn btn-${variant} btn-${size} ${loading ? 'btn-loading' : ''}`}
      {...rest}
    >
      {loading ? <span className="spinner" /> : children}
    </button>
  );
}

// Button.css: variantes con CSS o Tailwind
.btn { /* base */ padding: 0.5rem 1rem; border-radius: 8px; font-weight: 500; transition: all 0.2s; cursor: pointer; border: none; }
.btn-primary { background: var(--color-primario); color: white; }
.btn-primary:hover { background: var(--color-primario-hover); }
.btn-secondary { background: white; color: var(--color-texto); border: 1px solid var(--color-borde); }
.btn-danger { background: var(--color-error); color: white; }
.btn-ghost { background: transparent; color: var(--color-texto); }
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; }
.btn-md { padding: 0.5rem 1rem; font-size: 1rem; }
.btn-lg { padding: 0.75rem 1.5rem; font-size: 1.125rem; }
.btn-loading { opacity: 0.6; cursor: wait; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

// Uso: el mismo Button en TODA la app
<Button variant="primary" size="md">Guardar</Button>
<Button variant="danger" size="sm" onClick={eliminar}>Eliminar</Button>
<Button variant="secondary" disabled>Cancelar</Button>
<Button variant="primary" loading>Enviando...</Button>

Átomo Button: el componente más reutilizado de tu app

⭐ El Button es el átomo más importante: El átomo Button es EL más importante de tu app. Si tu Button está bien diseñado (con variantes y tamaños claros), el 80% de tus componentes lo usarán y tu UI será coherente. Si está mal diseñado (cada quien hace el suyo), tendrás 15 botones diferentes en tu app. Es el ejemplo perfecto de por qué Atomic Design importa: UN Button, 12 props, 4 variantes, 3 tamaños, usado en 50 lugares. Es la diferencia entre 'se ve hecho por un junior' y 'se ve hecho por un estudio de diseño'.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.6.3 Moléculas: SearchBar, FormField, Card

Las moléculas son combinaciones de átomos que forman una UNIDAD funcional. SearchBar = Input + Button + (opcional) Icon. FormField = Label + Input + (opcional) error message. Card = Image + Title + Description + (opcional) actions. Las moléculas tienen su PROPIA identidad: cuando ves una SearchBar, sabes que es 'el buscador', no solo 'un input con un botón al lado'.

Molécula SearchBar: input + botón con debounce integrado

// molecules/SearchBar.jsx
import { useState } from 'react';
import { useDebounce } from '../hooks/useDebounce';
import { Input } from '../atoms/Input';
import { Button } from '../atoms/Button';

export function SearchBar({ placeholder = 'Buscar...', onSearch, delay = 300 }) {
  const [termino, setTermino] = useState('');
  const terminoDebounced = useDebounce(termino, delay);

  // Cuando el debounced cambia, llama a onSearch
  if (onSearch && terminoDebounced) onSearch(terminoDebounced);

  return (
    <form className="search-bar" onSubmit={e => e.preventDefault()}>
      <Input
        type="search"
        placeholder={placeholder}
        value={termino}
        onChange={e => setTermino(e.target.value)}
      />
      <Button type="submit" variant="primary" size="md" disabled={!termino}>
        Buscar
      </Button>
    </form>
  );
}

// Molécula FormField: label + input + error message
// molecules/FormField.jsx
import { Input } from '../atoms/Input';

export function FormField({ label, name, type = 'text', value, onChange, error, required }) {
  return (
    <div className={`form-field ${error ? 'has-error' : ''}`}>
      <label htmlFor={name}>{label}{required && <span className="required">*</span>}</label>
      <Input id={name} name={name} type={type} value={value} onChange={onChange} />
      {error && <span className="error-message">{error}</span>}
    </div>
  );
}

// Uso:
<FormField
  label="Correo electrónico"
  name="email"
  type="email"
  value={email}
  onChange={e => setEmail(e.target.value)}
  error={errors.email}
  required
/>

Moléculas: SearchBar y FormField combinando átomos

⭐ Molécula = átomos con nombre y propósito claro: La regla para crear una molécula: si combinas 2+ átomos y el resultado tiene un NOMBRE claro ('SearchBar', 'FormField', 'Card'), es una molécula. Si solo es 'un div con un input y un botón', sigue siendo átomos sueltos. La prueba: ¿puedo reutilizar este componente en otra parte de la app con un nombre entendible? Si sí, es una molécula. Si no, déjalo como átomos sueltos. Es la diferencia entre un design system mantenible y un Frankenstein de divs.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.6.4 Organismos: Header, UserCard, ProductList

Los organismos son SECCIONES COMPLETAS con su propia lógica y estado. Header (logo + nav + search + user menu) es un organismo: tiene estado (usuario autenticado o no, menú abierto o cerrado), combina moléculas (SearchBar) y átomos (Button, Avatar), y tiene una responsabilidad clara: la navegación principal. Los organismos son el corazón de tu app.

Organismo Header: nav + search + user menu

// organisms/Header.jsx
import { Link, NavLink } from 'react-router-dom';
import { useState } from 'react';
import { Button } from '../atoms/Button';
import { Avatar } from '../atoms/Avatar';
import { SearchBar } from '../molecules/SearchBar';

export function Header({ usuario, onLogout }) {
  const [menuAbierto, setMenuAbierto] = useState(false);

  return (
    <header className="app-header">
      <Link to="/" className="logo">🌍 Atlas Mundial</Link>

      <nav className="nav-links">
        <NavLink to="/" end>Inicio</NavLink>
        <NavLink to="/paises">Países</NavLink>
        <NavLink to="/mapa">Mapa</NavLink>
        <NavLink to="/sobre">Sobre</NavLink>
      </nav>

      <SearchBar onSearch={(q) => console.log('Buscar:', q)} />

      {usuario ? (
        <div className="user-menu">
          <button onClick={() => setMenuAbierto(!menuAbierto)}>
            <Avatar src={usuario.avatar} alt={usuario.nombre} />
          </button>
          {menuAbierto && (
            <div className="dropdown">
              <Link to="/perfil">Mi perfil</Link>
              <Link to="/favoritos">Favoritos</Link>
              <button onClick={onLogout}>Cerrar sesión</button>
            </div>
          )}
        </div>
      ) : (
        <Button variant="primary" size="sm">Iniciar sesión</Button>
      )}
    </header>
  );
}

Organismo Header: nav + search + user menu con estado

⭐ Organismos = smart, átomos/moléculas = presentational: Los organismos son donde vive el ESTADO. Header tiene estado (menú abierto/cerrado, usuario logueado). PaisCard tiene estado (hover, favorito sí/no). ProductList tiene estado (productos cargados, filtro aplicado). Los átomos y moléculas son PRESENTACIONALES (solo reciben props, no tienen useState). Los organismos son SMART (tienen lógica, estado, fetch). Es la división de responsabilidades: presentational vs container components. Si un átomo tiene useState, probablemente debería ser un organismo.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.6.5 Templates y páginas: layout + instancias

Los templates son la ESTRUCTURA reutilizable sin contenido específico: layout principal con Header arriba, contenido en el medio, Footer abajo. Las páginas son las INSTANCIAS concretas del template con datos reales. Esta separación te permite cambiar todo el layout sin tocar las páginas, y cambiar una página sin tocar el layout. Es la separación de responsabilidades definitiva.

Template MainLayout: el esqueleto reutilizable

// templates/MainLayout.jsx: el template principal de tu app
import { Header } from '../organisms/Header';
import { Footer } from '../organisms/Footer';
import { Outlet } from 'react-router-dom';

export function MainLayout({ usuario, onLogout }) {
  return (
    <div className="app-layout">
      <Header usuario={usuario} onLogout={onLogout} />
      <main className="app-main">
        {/* Outlet renderiza el componente de la ruta activa */}
        <Outlet />
      </main>
      <Footer />
    </div>
  );
}

// pages/Home.jsx: una página concreta
import { useFetch } from '../hooks/useFetch';
import { SearchBar } from '../molecules/SearchBar';
import { PaisCard } from '../organisms/PaisCard';

export function Home() {
  const { data, cargando } = useFetch('https://restcountries.com/v3.1/all?fields=name,capital,population,flag,region,cca3');
  return (
    <div className="home-page">
      <h1>🌍 Explora el mundo</h1>
      <SearchBar onSearch={(q) => console.log(q)} />
      {cargando ? <p>Cargando...</p> : (
        <div className="paises-grid">
          {data?.slice(0, 20).map(pais => <PaisCard key={pais.cca3} pais={pais} />)}
        </div>
      )}
    </div>
  );
}

// App.jsx: usa MainLayout como template de TODAS las rutas
<Routes>
  <Route element={<MainLayout usuario={usuario} onLogout={logout} />}>
    <Route path="/" element={<Home />} />
    <Route path="/pais/:id" element={<DetallePais />} />
    <Route path="/sobre" element={<Sobre />} />
  </Route>
</Routes>

Template MainLayout y pages concretas con Outlet de React Router

⭐ <Outlet /> = DRY automático para layouts: La magia del <Outlet /> de React Router: MainLayout tiene UN punto donde se 'inyecta' el contenido de la ruta activa. Si tienes 10 páginas, las 10 usan el mismo MainLayout automáticamente. Es el patrón DRY (Don't Repeat Yourself) hecho framework: defines el layout UNA vez, todas las páginas lo heredan. Si cambias el footer, se actualiza en TODAS las páginas. Es la diferencia entre un sitio mantenible y uno donde cada página tiene su propio layout duplicado.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.6.6 Documentación de componentes con Storybook

Storybook es la herramienta estándar para documentar y testear componentes en aislamiento. Creas 'stories' para cada componente y cada variante, y tienes un catálogo vivo donde puedes ver cómo se ve tu Button primary, secondary, danger, disabled, loading, sin tener que navegar por toda tu app. Es lo que usan los equipos profesionales para mantener su design system. Vamos a configurarlo en Atlas Mundial.

Configurar Storybook en Vite + React

// Instalar: npx sb init --builder=vite
// Crea automáticamente la carpeta .storybook/ y stories/Button.stories.jsx

// stories/Button.stories.jsx: la documentación del Button
import { Button } from '../src/atoms/Button';

export default {
  title: 'Átomos/Button',
  component: Button,
  argTypes: {
    variant: { control: 'select', options: ['primary', 'secondary', 'danger', 'ghost'] },
    size: { control: 'select', options: ['sm', 'md', 'lg'] },
    disabled: { control: 'boolean' },
    loading: { control: 'boolean' },
  },
};

// Cada 'export const' es una 'story': una variante del componente
export const Primary = { args: { variant: 'primary', children: 'Guardar' } };
export const Secondary = { args: { variant: 'secondary', children: 'Cancelar' } };
export const Danger = { args: { variant: 'danger', children: 'Eliminar' } };
export const Ghost = { args: { variant: 'ghost', children: 'Más info' } };
export const Small = { args: { size: 'sm', children: 'Pequeño' } };
export const Large = { args: { size: 'lg', children: 'Grande' } };
export const Disabled = { args: { disabled: true, children: 'Deshabilitado' } };
export const Loading = { args: { loading: true, children: 'Cargando' } };

// Para correrlo: npm run storybook
// Abre en http://localhost:6006 y verás TODAS las variantes de tu Button en un solo lugar.
// Puedes cambiar las props con los controles y ver el cambio en tiempo real.

Storybook: catálogo vivo de componentes

⭐ Storybook = catálogo vivo + herramienta de trabajo: Storybook no es solo documentación: es HERRAMIENTA DE TRABAJO. Cuando agregas una nueva variante a Button, la agregas a Button.stories.jsx y todo el equipo la ve. Cuando un designer quiere saber 'qué tamaños tenemos', abre Storybook y ve todos. Cuando un developer nuevo llega, lee Storybook y entiende tu design system en 30 minutos. Es la inversión que más rinde en un equipo mediano-grande. Proyectos como Storybook UI, Material UI, Chakra UI, y shadcn/ui se crearon exactamente para esto. Si tu app tiene más de 20 componentes, Storybook vale la inversión.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

📚 Contenido ampliado

Material adicional, referencias externas verificadas y ejemplos extendidos.

🤖 AI Mission

Atomic Design es la metodología que usan los profesionales. No es opcional cuando escalas.

Misión: Toma tu app de Atlas Mundial y refactorízala con Atomic Design: identifica los átomos (Button, Input, etc.), moléculas (SearchBar, FormField, PaisCard), organismos (Header, MapaConDatos), templates (MainLayout), y páginas (Home, DetallePais). Configura Storybook para documentar tus componentes. Pídele a la IA que revise la jerarquía. Anota: ¿qué encontraste que era un átomo mal clasificado como organismo? ¿y viceversa?

Pasos sugeridos

  1. Parte del proyecto atlas-mundial del Tema 3.5.
  2. Crea la estructura de carpetas: src/atoms, src/molecules, src/organisms, src/templates, src/pages, src/hooks, src/utils.
  3. Mueve Button, Input, Label, Avatar, Spinner, Badge a atoms/. Asegúrate que NO tengan useState.
  4. Crea o mueve SearchBar, FormField, PaisCard, ClimaCard, PersonajeCard a molecules/.
  5. Crea o mueve Header, MapaConDatos, ListaPaisesSection a organisms/. Estos SÍ tienen useState.
  6. Crea templates/MainLayout.jsx con <Outlet /> de React Router.
  7. Crea stories/Button.stories.jsx con 8 variantes (Primary, Secondary, Danger, Ghost, Small, Large, Disabled, Loading).
  8. Crea stories para Input, Avatar, SearchBar, PaisCard, Header.
  9. Configura Storybook: npx sb init --builder=vite. Ejecuta npm run storybook.
  10. Refactoriza Home y DetallePais para usar SOLO componentes de atoms/molecules/organisms.
  11. Pídele a la IA: 'Tengo esta app refactorizada con Atomic Design. Audita la jerarquía: ¿hay átomos que deberían ser moléculas? ¿organismos que deberían ser átomos? Dame 3 sugerencias concretas.'
  12. Aplica las 3 correcciones.
  13. Anota en tu cuaderno: 3 cosas que descubriste al refactorizar (componentes mal clasificados, duplicación que no veías, etc.).

📓 Entregable: Capturas de Storybook mostrando todos los componentes y sus variantes, estructura de carpetas final con atoms/molecules/organisms, código de MainLayout, y media página de cuaderno con tu auditoría de la jerarquía.

🚫 Errores típicos de razonamiento

Error 1: Poner useState en un átomo (que debería ser presentacional).
Por qué: Los átomos son BLOQUES VISUALES puros. Si tienen useState, dejan de ser reutilizables: cada lugar que los usa tendría su propio estado. Es el error de mezclar 'presentational' (átomos) con 'container' (organismos). Si tu Input tiene useState, no es un átomo: es un organismo. La regla: átomos = solo props, organismos = useState y fetch. Si dudas, pregunta: '¿puedo usar este componente en 10 lugares sin que cada uno tenga su propio estado?' Si sí, es átomo.
Error 2: Tener 15 variantes de Button en lugar de un Button con props.
Por qué: Si tienes ButtonPrimary, ButtonSecondary, ButtonDanger, ButtonGhost, ButtonSmall, ButtonLarge, etc., estás CREANDO componentes cuando podrías haber usado UN Button con props. Cada componente extra es código que mantener, testear, y documentar. Un Button con props variant y size es 12 props, 4 variantes, 3 tamaños: 12 combinaciones posibles. Es el ejemplo perfecto de por qué Atomic Design + props > componentes separados.
Error 3: No documentar componentes con Storybook (o equivalente).
Por qué: Sin Storybook, los nuevos developers no saben qué componentes existen, qué props aceptan, o qué variantes tienen. Terminan creando ButtonNew porque no sabían que existía Button con variant='primary'. Storybook es la solución: catálogo vivo, búsqueda de componentes, controles interactivos. Para apps de 20+ componentes, vale la inversión de 1-2 días configurarlo. La alternativa barata: un archivo README.md con screenshots y tablas de props, pero Storybook es 10x mejor.
Error 4: Reutilizar la misma clase CSS en 5 componentes (duplicación).
Por qué: Si tienes .card en 5 archivos .module.css, y cambias el border-radius en uno, los otros 4 quedan con el viejo. La solución: (1) usar custom properties de CSS (--radio-md) que cambias UNA vez. (2) Usar un componente compartido en vez de clases compartidas. (3) Si la duplicación es inevitable, acepta que es un trade-off entre DRY y mantenibilidad. La regla: duplicar código es OK; duplicar estilos sin razón es un problema.
Error 5: Moléculas que hacen fetch (deberían ser organismos).
Por qué: Una molécula que hace fetch deja de ser presentacional: depende de una API, tiene estado de loading/error, y no es reutilizable en otros contextos. SearchBar es molécula: solo recibe props y emite eventos. PaisCard que hace fetch a la API para obtener detalles es organismo: tiene estado, depende de la API, y tiene su propia responsabilidad. La regla: si el componente tiene useEffect, useState de datos del API, o maneja errores de fetch, es organismo. Si solo recibe props y emite eventos, es molécula o átomo.

🧪 Laboratorio práctico

Un design system no es documentación: es código que se ejecuta.

Laboratorio: Atlas Mundial refactorizado con Atomic Design + Storybook

Objetivo: Refactorizar la app de Atlas Mundial del Tema 3.5 con la metodología Atomic Design: atoms/, molecules/, organisms/, templates/, pages/. Configurar Storybook para documentar todos los componentes. El resultado: una arquitectura escalable, mantenible, y documentada.

Pasos

  1. Crea estructura: mkdir src/atoms src/molecules src/organisms src/templates src/pages src/hooks src/utils src/stories.
  2. Mueve Button, Input, Label, Avatar, Spinner, Badge a atoms/. Cada uno solo con props, sin useState.
  3. Crea o mueve SearchBar, FormField, PaisCard, ClimaCard, PersonajeCard a molecules/.
  4. Crea o mueve Header, Footer, MapaConClima, ListaPaises a organisms/. Estos SÍ tienen useState y fetch.
  5. Crea templates/MainLayout.jsx con <Outlet />.
  6. Refactoriza pages/Home.jsx y pages/DetallePais.jsx para que SOLO usen componentes de atoms/molecules/organisms/templates.
  7. Instala Storybook: npx sb init --builder=vite. Acepta la configuración por defecto.
  8. Crea stories/Button.stories.jsx con 8 variantes. Crea stories para Input, Avatar, SearchBar, PaisCard.
  9. Ejecuta: npm run storybook. Abre http://localhost:6006 y verifica que TODOS los componentes y sus variantes se ven.
  10. Ajusta las props con los controles de Storybook. Verifica que cada variante se ve bien.
  11. Verifica accesibilidad: cada componente tiene su label, role, focus visible.
  12. Haz commit: 'refactor: estructura Atomic Design + Storybook configurado'.

📓 Entregable: Capturas de Storybook con todos los componentes y variantes, código de atoms/molecules/organisms/templates/pages, y commit en Git.

📓 Tu cuaderno: Anota pseudocódigo, diagramas, errores que encontraste y respuestas a "explica sin código". La escritura manual refuerza tu razonamiento. Tu profesor puede pedirte que subas fotos de páginas específicas.