Capítulo 01

¿Qué es React?

Una librería JavaScript para construir interfaces de usuario a partir de componentes reutilizables. No es un framework completo — es deliberadamente pequeño y enfocado en la capa de vista.

Creado por Meta en 2013, React cambió la forma de pensar en interfaces web. En lugar de manipular el DOM directamente (con jQuery o JavaScript puro), React te permite describir cómo debe verse la UI y deja que la librería actualice el DOM de la forma más eficiente posible.

🧩
Basado en Componentes
Todo es un componente: botones, formularios, páginas enteras. Se combinan como piezas de Lego.
Virtual DOM
React mantiene una copia virtual del DOM en memoria y actualiza solo lo que cambió. Rápido y eficiente.
🔁
Flujo Unidireccional
Los datos fluyen en una sola dirección (padre → hijo), haciendo el estado predecible y debuggeable.
🌐
Ecosistema Enorme
React Native (móvil), Next.js (SSR), Remix, Expo. Una habilidad, múltiples plataformas.
ℹ️
React vs Framework completo React solo maneja la UI. Para routing usas React Router o Next.js. Para estado global usas Zustand, Redux o Context API. Para fetching usas TanStack Query. Esta modularidad es su mayor fortaleza — y también la curva de aprendizaje inicial.

Capítulo 02

JSX y el Virtual DOM

JSX es una extensión de JavaScript que permite escribir HTML dentro de JS. Babel lo transforma en llamadas a React.createElement().

Lo que escribes (JSX) vs lo que ejecuta JS JSX
// ✍️ Lo que escribes en tu editor:
const elemento = (
  <div className="tarjeta">
    <h1>Hola, {nombre}</h1>
    <p>Bienvenido a React</p>
  </div>
);

// ⚙️ Lo que Babel genera (automáticamente):
const elemento = React.createElement(
  'div', { className: 'tarjeta' },
  React.createElement('h1', null, 'Hola, ', nombre),
  React.createElement('p',  null, 'Bienvenido a React')
);

Reglas de JSX:

Reglas importantes de JSX JSX
// 1. Un solo elemento raíz (o Fragment)
return (
  <>                       // Fragment ← no genera HTML
    <h1>Título</h1>
    <p>Párrafo</p>
  </>
);

// 2. className en lugar de class
<div className="contenedor">...</div>

// 3. Expresiones JS entre llaves {}
<p>Total: {precio * cantidad}</p>
<p>Fecha: {new Date().toLocaleDateString('es-MX')}</p>

// 4. Renderizado condicional
{isLoading && <Spinner />}
{error ? <Error msg={error} /> : <Datos />}

// 5. Atributos en camelCase
<input type="text" onChange={handleChange} autoFocus />

Capítulo 03

Componentes Funcionales

Un componente es simplemente una función que recibe datos (props) y devuelve JSX. Es la unidad fundamental de React moderno.

components/TarjetaProducto.jsx JSX
// Componente funcional — la forma moderna (React 16.8+)
function TarjetaProducto({ nombre, precio, stock, imagen }) {
  const agotado = stock === 0;

  return (
    <div className={`tarjeta ${agotado ? 'agotado' : ''}`}>
      <img src={imagen} alt={nombre} />
      <h3>{nombre}</h3>
      <p>{formatearPeso(precio)}</p>
      {agotado
        ? <span className="badge rojo">Agotado</span>
        : <button>Agregar al carrito</button>
      }
    </div>
  );
}

// Uso del componente (como si fuera una etiqueta HTML)
<TarjetaProducto
  nombre="Arrachera Premium"
  precio={189.50}
  stock={12}
  imagen="/img/arrachera.jpg"
/>
💡
Convención de nombres Los componentes siempre empiezan con mayúscula (TarjetaProducto). React usa esto para distinguir entre etiquetas HTML nativas (<div>) y componentes propios (<TarjetaProducto>). Un componente con minúscula sería interpretado como etiqueta HTML.

Capítulo 04

Props — Datos de Entrada

Las props (properties) son la forma en que los componentes reciben datos del exterior. Son de solo lectura — un componente nunca modifica sus propias props.

Props: tipos, defaults y children JSX
// Props con desestructuración + valores por defecto
function Boton({
  children,                     // contenido entre etiquetas
  variante = 'primario',         // default value
  tamaño   = 'md',
  disabled = false,
  onClick,                      // función callback
}) {
  return (
    <button
      className={`btn btn-${variante} btn-${tamaño}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// Diferentes usos del mismo componente:
<Boton>Guardar</Boton>
<Boton variante="peligro" onClick={eliminar}>Eliminar</Boton>
<Boton disabled>Procesando...</Boton>

Capítulo 05

useState — Estado Local

El hook fundamental. Permite que un componente "recuerde" valores entre renders. Cuando el estado cambia, React re-renderiza el componente automáticamente.

useState — anatomía completa HOOK
import { useState } from 'react';

function Contador() {
  //  [valor actual]  [función actualizadora]    [valor inicial]
  const [cuenta, setCuenta] = useState(0);

  // Actualización directa
  const incrementar = () => setCuenta(cuenta + 1);

  // Actualización funcional (recomendada cuando depende del valor previo)
  const incrementarSeguro = () => setCuenta(prev => prev + 1);

  return (
    <>
      <p>Cuenta: {cuenta}</p>
      <button onClick={incrementarSeguro}>+</button>
      <button onClick={() => setCuenta(0)}>Reset</button>
    </>
  );
}

// Estado con objetos — siempre copia, nunca mutas
const [usuario, setUsuario] = useState({ nombre: '', email: '' });
// ✅ Correcto:
setUsuario(prev => ({ ...prev, nombre: 'Ana' }));
// ❌ Incorrecto (mutación directa no dispara re-render):
// usuario.nombre = 'Ana';
Demo interactivo — useState ▶ LIVE
cuenta
0
Nunca mutar el estado directamente React usa comparación por referencia para detectar cambios. Si mutas el objeto/array directamente sin crear una copia, React no detecta el cambio y no re-renderiza. Siempre usa el setter: setState(newValue) o el patrón funcional setState(prev => ...).

Capítulo 06

useEffect — Efectos Secundarios

Para operaciones que ocurren "fuera" del renderizado: fetch de datos, subscripciones a eventos, manipulación del DOM, timers.

Fase 1
Render
React ejecuta la función del componente y calcula el JSX a mostrar.
Fase 2
Commit al DOM
React actualiza el DOM real con los cambios calculados.
Fase 3
useEffect corre
Después de pintar, React ejecuta los efectos registrados.
Fase 4
Cleanup
Al des-montar o antes del próximo efecto: limpia suscripciones, timers.
useEffect — los 3 patrones HOOK
import { useState, useEffect } from 'react';

function PerfilUsuario({ userId }) {
  const [usuario, setUsuario] = useState(null);
  const [loading, setLoading]  = useState(true);

  // ── Patrón 1: Sin array de dependencias ───────────────
  // Corre después de CADA render
  useEffect(() => {
    document.title = `Perfil: ${usuario?.nombre}`;
  });

  // ── Patrón 2: Array vacío [] ──────────────────────────
  // Corre solo UNA VEZ al montar el componente
  useEffect(() => {
    console.log('Componente montado');
    return () => console.log('Componente desmontado (cleanup)');
  }, []);

  // ── Patrón 3: Con dependencias [dep1, dep2] ───────────
  // Corre cuando userId cambia
  useEffect(() => {
    let cancelado = false; // evitar race conditions

    setLoading(true);
    fetch(`/api/usuarios/${userId}`)
      .then(r => r.json())
      .then(data => {
        if (!cancelado) {
          setUsuario(data);
          setLoading(false);
        }
      });

    return () => { cancelado = true; }; // cleanup
  }, [userId]); // ← vuelve a correr si userId cambia

  if (loading) return <Spinner />;
  return <div>{usuario.nombre}</div>;
}
Demo — useEffect + fetch de API ▶ LIVE
Haz clic en el botón para simular un fetch con useEffect…

Capítulo 07

useRef — Referencias y Valores Persistentes

useRef tiene dos usos: acceder a elementos del DOM directamente, y guardar valores que persisten entre renders sin disparar un re-render.

useRef — dos usos principales HOOK
import { useRef, useEffect, useState } from 'react';

// ── Uso 1: Referencia al DOM ──────────────────────────
function CampoConFocus() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // foco automático al montar
  }, []);

  const limpiar = () => {
    inputRef.current.value = '';
    inputRef.current.focus();
  };

  return <input ref={inputRef} placeholder="Buscar producto..." />;
}

// ── Uso 2: Valor persistente sin re-render ────────────
function Cronometro() {
  const [tiempo, setTiempo] = useState(0);
  const intervalRef = useRef(null); // guarda el ID del interval

  const iniciar = () => {
    intervalRef.current = setInterval(() => {
      setTiempo(t => t + 1);
    }, 1000);
  };

  const detener = () => clearInterval(intervalRef.current);

  return (
    <>
      <p>{tiempo}s</p>
      <button onClick={iniciar}>Iniciar</button>
      <button onClick={detener}>Detener</button>
    </>
  );
}

Capítulo 08

useContext — Estado Global Simple

Context API evita "prop drilling" — pasar props por múltiples niveles de componentes solo para que lleguen al fondo. Ideal para tema, usuario autenticado, idioma.

context/AuthContext.jsx — patrón completo HOOK
import { createContext, useContext, useState } from 'react';

// 1. Crear el contexto
const AuthContext = createContext(null);

// 2. Crear el Provider (envuelve la app o sección que necesita acceso)
export function AuthProvider({ children }) {
  const [usuario, setUsuario] = useState(null);

  const login = async (email, password) => {
    const data = await fetchLogin(email, password);
    setUsuario(data);
  };

  const logout = () => setUsuario(null);

  return (
    <AuthContext.Provider value={{ usuario, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// 3. Hook personalizado para consumir el contexto
export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth debe usarse dentro de AuthProvider');
  return ctx;
}

// 4. Uso en cualquier componente anidado (sin pasar props)
function Header() {
  const { usuario, logout } = useAuth();
  return (
    <header>
      <span>Hola, {usuario.nombre}</span>
      <button onClick={logout}>Cerrar sesión</button>
    </header>
  );
}

Capítulo 09

useMemo y useCallback

Optimización de performance: evitar recalcular valores costosos o recrear funciones en cada render. Úsalos solo cuando hay un problema real de rendimiento.

useMemouseCallback memoización optimización
useMemo y useCallback HOOK
import { useMemo, useCallback } from 'react';

function ReporteVentas({ ventas, filtro }) {
  // useMemo: memoriza el resultado de un cálculo costoso
  // Solo recalcula si [ventas, filtro] cambian
  const ventasFiltradas = useMemo(() => {
    return ventas
      .filter(v => v.sucursalId === filtro.sucursal)
      .filter(v => v.fecha >= filtro.desde)
      .reduce((acc, v) => acc + v.total, 0);
  }, [ventas, filtro]);

  // useCallback: memoriza una función para evitar recrearla
  // Útil cuando la función se pasa como prop a componentes hijos
  const handleExportar = useCallback(() => {
    exportarCSV(ventasFiltradas);
  }, [ventasFiltradas]); // ← solo cambia si ventasFiltradas cambia

  return (
    <>
      <p>Total: {ventasFiltradas}</p>
      <BotonExportar onExportar={handleExportar} /> // no re-renderiza innecesariamente
    </>
  );
}
⚠️
No optimices prematuramente useMemo y useCallback tienen un costo de memoria. No los uses en todos lados "por si acaso". Úsalos cuando profiling muestre renders lentos, cuando una función se pasa a un componente memoizado con React.memo(), o cuando el cálculo es genuinamente costoso (filtrar/ordenar miles de registros).

Capítulo 10

Listas y Keys

Renderizar colecciones de datos es de las tareas más comunes. React necesita una key única en cada elemento para identificar cambios eficientemente.

Listas — map(), key, y filtrado JSX
function ListaProductos({ productos, busqueda }) {
  const filtrados = productos.filter(p =>
    p.nombre.toLowerCase().includes(busqueda.toLowerCase())
  );

  if (filtrados.length === 0) {
    return <p>Sin resultados para "{busqueda}"</p>;
  }

  return (
    <ul>
      {filtrados.map(producto => (
        <li key={producto.id}>  {/* key: ID único del backend */}
          <TarjetaProducto
            nombre={producto.nombre}
            precio={producto.precio}
          />
        </li>
      ))}
    </ul>
  );
}

// ❌ NO uses el índice como key (a menos que la lista sea estática)
// productos.map((p, index) => <li key={index}>)  ← causa bugs en reordenamiento
// ✅ Usa siempre el ID único de negocio: key={producto.id}

Capítulo 11

Formularios Controlados

En React, el estado del formulario vive en useState (componente controlado). El input siempre muestra el estado, y cada cambio actualiza el estado.

FormularioVenta.jsx — formulario controlado JSX
function FormularioVenta() {
  const [form, setForm] = useState({
    producto: '', cantidad: 1, precio: '',
  });
  const [errores, setErrores] = useState({});

  // Un solo handler para todos los inputs
  const handleChange = (e) => {
    const { name, value, type } = e.target;
    setForm(prev => ({
      ...prev,
      [name]: type === 'number' ? Number(value) : value,
    }));
  };

  const validar = () => {
    const e = {};
    if (!form.producto) e.producto = 'Requerido';
    if (form.cantidad < 1) e.cantidad = 'Mínimo 1';
    if (!form.precio || isNaN(form.precio)) e.precio = 'Precio inválido';
    setErrores(e);
    return Object.keys(e).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validar()) {
      procesarVenta(form);
      setForm({ producto: '', cantidad: 1, precio: '' });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="producto"
        value={form.producto}
        onChange={handleChange}
      />
      {errores.producto && <span>{errores.producto}</span>}
      <button type="submit">Registrar Venta</button>
    </form>
  );
}
Demo — Lista con formulario controlado ▶ LIVE
Sin tareas. Agrega la primera ↑

Capítulo 12

Custom Hooks

Los custom hooks son la forma más elegante de reutilizar lógica con estado. Son funciones que usan otros hooks y empiezan con use.

hooks/useFetch.js — hook de fetching CUSTOM HOOK
import { useState, useEffect } from 'react';

// Custom hook: encapsula lógica de fetch reutilizable
export function useFetch(url) {
  const [data,    setData]    = useState(null);
  const [loading, setLoading] = useState(true);
  const [error,   setError]   = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    setLoading(true);
    fetch(url, { signal: controller.signal })
      .then(r => { if(!r.ok) throw new Error(r.statusText); return r.json(); })
      .then(d => { setData(d); setError(null); })
      .catch(e  => { if(e.name !== 'AbortError') setError(e.message); })
      .finally(() => setLoading(false));

    return () => controller.abort(); // cleanup
  }, [url]);

  return { data, loading, error };
}

// ──────────────────────────────────────────────────────
// Uso: limpio, sin lógica de fetch en el componente
function ListaSucursales() {
  const { data, loading, error } = useFetch('/api/sucursales');

  if (loading) return <Spinner />;
  if (error)   return <p>Error: {error}</p>;

  return data.map(s => <TarjetaSucursal key={s.id} sucursal={s} />);
}

// ─── Más custom hooks útiles ───────────────────────────
// useLocalStorage(key, initialValue) → persiste estado
// useDebounce(value, delay)          → input de búsqueda
// useOnClickOutside(ref, handler)    → cerrar dropdowns
// useMediaQuery('(min-width:768px)') → responsive

Capítulo 13

Estructura de Proyecto

Arquitectura recomendada — Feature-based ESTRUCTURA
src/
├── features/            ← organiza por módulo de negocio
│   ├── ventas/
│   │   ├── components/   ← componentes del módulo
│   │   ├── hooks/        ← custom hooks del módulo
│   │   ├── services/     ← llamadas a API
│   │   ├── types.ts      ← TypeScript interfaces
│   │   └── index.js      ← exports públicos del módulo
│   ├── inventario/
│   ├── reportes/
│   └── empleados/
│
├── components/          ← componentes compartidos (UI kit)
│   ├── Button.jsx
│   ├── Modal.jsx
│   ├── Table.jsx
│   └── Spinner.jsx
│
├── hooks/               ← custom hooks globales
│   ├── useFetch.js
│   ├── useDebounce.js
│   └── useAuth.js
│
├── context/             ← Context providers globales
│   ├── AuthContext.jsx
│   └── ThemeContext.jsx
│
├── services/            ← cliente HTTP, helpers de API
│   └── api.js
│
└── App.jsx              ← routes + providers globales

Capítulo 14

ERP Carnicerías — React en Acción

Así se aplican todos los conceptos anteriores en el proyecto real del ERP, combinado con el framework MoRe (React + Moleculer).

features/pos/components/PantallaVenta.jsx REAL
import { useState, useEffect, useCallback } from 'react';
import { useMoleculer }   from '../../../hooks/useMoleculer';
import { useBascula }     from '../hooks/useBascula';
import { useOfflineSync } from '../../../hooks/useOfflineSync';

export function PantallaVenta({ sucursalId }) {
  const [items,     setItems]     = useState([]);
  const [cliente,   setCliente]   = useState(null);
  const [procesando,setProcesando] = useState(false);

  // Custom hook: conecta con Moleculer service
  const { call } = useMoleculer();

  // Custom hook: lee peso de báscula en tiempo real
  const { peso, unidad } = useBascula({ port: 'COM3', baudRate: 9600 });

  // Custom hook: encola ventas para sync si hay offline
  const { encolarVenta } = useOfflineSync(sucursalId);

  // Calcular total con useMemo (se recalcula solo al cambiar items)
  const total = useMemo(
    () => items.reduce((s, i) => s + i.precio * i.cantidad, 0),
    [items]
  );

  const cobrar = useCallback(async () => {
    setProcesando(true);
    try {
      // Llama al Moleculer Sales Service
      const venta = await call('sales.registrarVenta', {
        sucursalId, items, clienteId: cliente?.id, total,
      });

      // BullMQ encolará el CFDI automáticamente (en el service)
      await encolarVenta(venta); // sync si hubo offline
      setItems([]);              // limpiar ticket
    } finally {
      setProcesando(false);
    }
  }, [items, cliente, total, sucursalId]);

  return (
    <div className="pos-layout">
      <PanelProductos onAgregar={item => setItems(p => [...p, item])} />
      <Ticket items={items} total={total} pesoBascula={peso} />
      <BotonCobrar onCobrar={cobrar} disabled={procesando} />
    </div>
  );
}

Capítulo 15

Buenas Prácticas

🎯
Un componente, una responsabilidad
Si un componente hace demasiadas cosas, divídelo. Si tienes más de ~150 líneas, considera separarlo.
⬆️
Estado lo más abajo posible
El estado debe vivir en el componente más bajo de la jerarquía que lo necesite. Evita state global innecesario.
🪝
Extrae lógica a custom hooks
Cuando useEffect + useState se repite o es complejo, muévelo a un custom hook nombrado descriptivamente.
🔑
Keys de ID real, nunca index
El índice del array como key causa bugs sutiles cuando la lista se filtra, ordena o modifica.
🧪
Testea comportamiento, no implementación
Con React Testing Library: prueba lo que el usuario ve y hace, no los internos del componente.
📝
TypeScript desde el inicio
Las props tipadas previenen más bugs que cualquier otra práctica. Añadir TS después es doloroso.
🚀
Stack recomendado para proyectos nuevos Vite + React 18 + TypeScript · React Router v6 o Next.js 14 · TanStack Query para server state · Zustand para client state · Zod para validación de schemas · Vitest + React Testing Library para tests.