🔎 Buscar

🎨 Frontend — contenido completo

HTML semántico, CSS moderno (Flexbox, Grid, layout, responsive), JavaScript profundo y el event loop, TypeScript, React/Next.js, testing, accesibilidad, performance y arquitectura. Teoría, código y práctica.

Frontend📖 Contenido

Frontend — contenido completo

El frontend profesional ya no es «maquetar»: es HTML semántico + CSS a fondo + JavaScript profundo (con TypeScript) + un framework dominante + una capa de ingeniería (testing, accesibilidad, performance, arquitectura). Esta página te da el contenido completo en orden: fundamentos web primero, framework después.

Las wikis del sitio tienen detalle: CSS moderno y El event loop de JS.

HTML: la semántica es la base

HTML no es «el esqueleto de la página»: es la estructura con significado. Usar la etiqueta correcta le da significado a los motores de búsqueda, a los lectores de pantalla (accesibilidad) y a otros desarrolladores.

Etiquetas semánticas

<header>   <!-- cabecera del sitio -->
<nav>      <!-- navegación -->
<main>     <!-- el contenido principal (una sola por página) -->
<article>  <!-- contenido autocontenido (post, noticia) -->
<section>  <!-- sección temática -->
<aside>    <!-- contenido complementario (sidebar) -->
<footer>   <!-- pie de página -->
<!-- MAL: divs sin significado -->
<div class="header"><div class="title">Mi Blog</div></div>

<!-- BIEN: semántica correcta -->
<header><h1>Mi Blog</h1></header>
<main>
  <article>
    <h2>Cómo aprendí CSS</h2>
    <p>...</p>
  </article>
</main>

Jerarquía de títulos (H1 → H6)

<main>
  <h1>Título de la página</h1>      <!-- uno solo por página -->
  <section>
    <h2>Sección</h2>
    <h3>Subsección</h3>
  </section>
</main>

💡 Regla: un solo h1 por página; saltarse niveles (h1h3) rompe la estructura para lectores de pantalla. Los títulos jerárquicos son el mapa de la página.

Formularios accesibles

<label for="email">Email</label>
<input type="email" id="email" name="email" required autocomplete="email">

⚠️ label con for: el texto del label debe asociarse al input (click en el label = enfoca el input). Un formulario sin labels es inaccesible.

CSS moderno

La teoría a fondo está en wiki: CSS moderno. Lo esencial:

El modelo de caja (box model)

Todo elemento es una caja: content + padding + border + margin.

            ┌───── margin ─────┐
            │ ┌─── border ───┐ │
            │ │ ┌─ padding ─┐│ │
            │ │ │ content   ││ │
            │ │ └───────────┘│ │
            │ └─────────────┘ │
            └─────────────────┘
.box {
  width: 200px;
  padding: 16px;        /* espacio interno */
  border: 1px solid;    /* borde */
  margin: 20px;         /* espacio externo */
  box-sizing: border-box; /* ⚠️ el estándar moderno: width incluye padding/border */
}

Flexbox: layout en una dimensión

Flexbox distribuye elementos en una fila o una columna.

.nav {
  display: flex;
  justify-content: space-between;  /* horizontal */
  align-items: center;             /* vertical */
  gap: 16px;                       /* separación */
}
justify-content:  distribuye a lo LARGO del eje principal
align-items:       distribuye a lo ANCHO del eje cruzado
flex-wrap: wrap    permite saltar de línea
flex: 1            el elemento crece para llenar

Grid: layout en dos dimensiones

Grid distribuye en filas y columnas a la vez.

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 24px;
}
repeat(auto-fit, minmax(250px, 1fr)):
  → tantas columnas como quepan, mínimo 250px cada una,
    y cada una crece igual (1fr). Layout responsive sin media queries.

Responsive y media queries

/* Mobile-first: base para móvil, luego mejoras */
.container { padding: 16px; }

@media (min-width: 768px) {
  .container { padding: 32px; }
}

💡 Unidades: rem (relativa al tamaño de raíz, la estándar para texto), em (relativa al padre), %, vh/vw (viewport), ch (ancho de caracteres). Nunca píxeles fijos para texto: rompe el zoom y la accesibilidad.

Custom properties (variables CSS)

:root {
  --color-primario: #2563eb;
  --radio: 8px;
}
.boton {
  background: var(--color-primario);
  border-radius: var(--radio);
}

JavaScript profundo

JS es un lenguaje con matices que casi nadie conoce bien a la primera. Lo esencial que debes dominar antes de cualquier framework:

Tipos y coerción

typeof "hola";       // "string"
typeof 42;           // "number"
typeof true;         // "boolean"
typeof null;         // "object" (bug histórico de JS, no lo toques)
typeof undefined;    // "undefined"
typeof {};           // "object"
typeof [];           // "object"
typeof function(){}; // "function"

⚠️ Coerción: JS convierte tipos automáticamente ("1" + 1 === "11"). Usa siempre comparación estricta === (compara valor Y tipo) y nunca ==.

1 == "1"    // true  (coerción implícita)
1 === "1"   // false (comparación estricta, la correcta)

Funciones y closures

Un closure es una función que «recuerda» el entorno donde se creó, aunque esa función ya haya terminado.

function crearContador() {
  let n = 0;                    // 'n' vive en el closure
  return function () {
    n += 1;
    return n;
  };
}
const contar = crearContador();
contar(); // 1
contar(); // 2
contar(); // 3

this y arrow functions

const obj = {
  valor: 42,
  normal() { return this.valor; },   // this = obj (contexto de llamada)
  flecha: () => this.valor,          // this = contexto de creación (fuera)
};

💡 Regla: las arrow functions no tienen su propio this (heredan el del entorno). Los callbacks de eventos y setTimeout clásicos pierden el this — por eso las arrow functions dominan el JS moderno.

Async: Promesas y async/await

// Promesa: un valor que llegará (éxito) o fallará
const datos = fetch("/api/usuarios")
  .then((r) => r.json())
  .then((usuarios) => console.log(usuarios))
  .catch((err) => console.error(err));

// async/await: la forma legible moderna
async function cargarUsuarios() {
  try {
    const r = await fetch("/api/usuarios");
    const usuarios = await r.json();
    return usuarios;
  } catch (err) {
    console.error(err);
  }
}

💡 JS es de un solo hilo (event loop): el await no bloquea; devuelve el control al event loop mientras espera. El detalle completo en wiki: Event loop.

map, filter, reduce (el pan de cada día)

const numeros = [1, 2, 3, 4, 5];

numeros.map((n) => n * 2);        // [2, 4, 6, 8, 10]   transformar
numeros.filter((n) => n % 2 === 0); // [2, 4]            filtrar
numeros.reduce((acc, n) => acc + n, 0); // 15             acumular

TypeScript: JS con tipos

TypeScript es JavaScript + un sistema de tipos en tiempo de compilación. No existe en el navegador: se compila a JS (y con type erasure). Te da dos cosas: detección de errores antes de ejecutar y autocompletado.

interface Usuario {
  id: number;
  nombre: string;
  email: string;
  activo?: boolean;      // opcional
}

function formatear(u: Usuario): string {
  return `${u.nombre} <${u.email}>`;
}

// Error en compilación, no en ejecución:
formatear({ id: 1, nombre: "Ana" });  // falta 'email' → TS lo pilla

Narrowing y uniones

function procesar(valor: string | number): number {
  if (typeof valor === "string") {
    return valor.length;   // aquí valor es string (narrowing)
  }
  return valor * 2;        // aquí valor es number
}

💡 Todo el detalle en wiki: TypeScript tipos y Fundamentos TS.

React y el render

React es una librería para construir interfaces declarativas: dices cómo debe verse el UI para cada estado, y React lo actualiza por ti.

Componentes y props

function TarjetaUsuario({ usuario }) {   // props = entrada
  return (
    <div className="tarjeta">
      <h3>{usuario.nombre}</h3>
      <p>{usuario.email}</p>
    </div>
  );
}

// Uso:
<TarjetaUsuario usuario={{ nombre: "Ana", email: "ana@mail.com" }} />

Estado (state)

import { useState } from "react";

function Contador() {
  const [cuenta, setCuenta] = useState(0);
  return (
    <button onClick={() => setCuenta(cuenta + 1)}>
      Clicks: {cuenta}
    </button>
  );
}

💡 El contrato de React: el componente se re-renderiza cuando cambian sus props o su state. La actualización del DOM real es reactiva (virtual DOM / reconciliation): React calcula qué cambió y aplica solo eso.

Efectos (useEffect)

import { useEffect, useState } from "react";

function Usuarios() {
  const [usuarios, setUsuarios] = useState([]);
  const [cargando, setCargando] = useState(true);

  useEffect(() => {
    fetch("/api/usuarios")
      .then((r) => r.json())
      .then((d) => { setUsuarios(d); setCargando(false); });
  }, []);   // [] = solo una vez al montar

  if (cargando) return <p>Cargando…</p>;
  return <ul>{usuarios.map((u) => <li key={u.id}>{u.nombre}</li>)}</ul>;
}

⚠️ Regla de React moderna: useEffect es para sincronizar con el exterior (fetch, suscripciones), no para computar cosas derivadas del estado (eso se hace en el render). Los derived state y los hooks modernos (use, useMemo, useCallback) son el tema de los cursos avanzados.

Estado global y data fetching

  • Estado de servidor (datos del API): TanStack Query — caché, retries, invalidación. El estándar.
  • Estado de UI global (sesión, tema, carrito): Context + hooks, Zustand, Redux Toolkit, Jotai.
  • Regla: server state no va en Redux; se gestiona con una librería de fetching.

Next.js: el framework de React de producción

Next.js añade a React: routing por archivos, render en servidor (SSR/SSG), Server Components, Server Actions, optimización (imágenes, fonts, caching).

SSG (estático):    HTML generado en build → rápido, para contenido
SSR (servidor):    HTML generado por petición → dinámico
ISR (incremental): estático con revalidación → contenido semi-dinámico
Client:            todo en el navegador → apps interactivas

Testing de frontend

El mapa (mismo que backend, adaptado):

Nivel Herramienta Qué verifica
Unit Vitest Funciones y componentes aislados
Component/integration Testing Library Comportamiento: render, eventos, resultados
E2E Playwright Flujos completos en navegadores reales
// testing-library: testea comportamiento, no implementación
import { render, screen, fireEvent } from "@testing-library/react";
import Contador from "./Contador";

test("el contador incrementa al hacer clic", () => {
  render(<Contador />);
  fireEvent.click(screen.getByRole("button"));
  expect(screen.getByText("Clicks: 1")).toBeInTheDocument();
});

💡 Filosofía Testing Library: testea lo que el usuario ve y hace (roles, texto), no los detalles internos. Tests que no se rompen por refactors.

Accesibilidad (a11y)

La accesibilidad hace que tu web funcione para todo el mundo, incluidos usuarios con lectores de pantalla, teclado y contraste bajo. Es buena ingeniería y buena ley (WCAG, a nivel de obligación legal en muchos países).

Lo mínimo que debes hacer

  1. HTML semántico (h1-h6, nav, main, article) → el lector de pantalla lo entiende gratis.
  2. Labels en inputs (label for).
  3. Texto alternativo en imágenes (alt).
  4. Contraste suficiente (WCAG AA: 4.5:1 en texto normal).
  5. Navegación por teclado: todo debe ser operable sin ratón (tab, focus visible).
  6. ARIA solo cuando HTML no basta (tabs, modales, combobox).
<button aria-label="Cerrar el diálogo">×</button>
<div role="dialog" aria-labelledby="titulo-modal" aria-modal="true">…</div>

⚠️ Regla de ARIA: «no usar ARIA es mejor que usar mal ARIA». Primero intenta con HTML nativo; ARIA solo para componentes que HTML no cubre.

Performance: Core Web Vitals

Google mide el rendimiento real del usuario con los Core Web Vitals:

Métrica Qué mide Objetivo
LCP (Largest Contentful Paint) Cuándo carga el contenido principal Menos de 2.5 s
INP (Interaction to Next Paint) Capacidad de respuesta a interacciones Menos de 200 ms
CLS (Cumulative Layout Shift) Cuánto se mueve la página al cargar Menos de 0.1

Técnicas para mejorarlos

Problema Solución
JS enorme Split de código (lazy import), tree-shaking, minificación
Imágenes pesadas WebP/AVIF, lazy loading, tamaños correctos, CDN
Bloqueo del hilo principal Dividir trabajo, requestIdleCallback, Web Workers
Redirecciones y requests Menos requests, HTTP/2/3, precarga de fuentes críticas
Layout shift Tamaños reservados en imágenes (width/height, aspect-ratio)
<!-- imágenes: dimensiones fijas para no saltar (CLS) + lazy -->
<img src="foto.webp" width="800" height="600" loading="lazy" alt="...">

💡 Mide con PageSpeed Insights, Lighthouse (DevTools) y web-vitals (npm). Optimiza primero el LCP (lo que el usuario ve primero), luego INP, luego CLS.

Terminología que debes dominar

Término En una frase
Semántica Significado de las etiquetas HTML
Box model content + padding + border + margin
Flexbox / Grid Layout 1D / 2D
Responsive Se adapta a cualquier pantalla
DOM El árbol de elementos de la página
Event loop El hilo único de JS procesando tareas
Closure Función que recuerda su entorno
Promise Valor que llegará o fallará
async/await Forma legible de manejar promesas
Componente Unidad de UI reutilizable
Props / state Entrada / estado interno de un componente
Render Producir el UI a partir del estado
Hydration React «encendiendo» HTML del servidor
SSR / SSG / ISR Render en servidor / estático / incremental
a11y Accesibilidad
WCAG / ARIA Estándar de accesibilidad / atributos auxiliares
Core Web Vitals LCP, INP, CLS
Tree-shaking Eliminar código no usado del bundle

Práctica propuesta

  1. Construye una página semántica con header, nav, main, article, footer y un formulario accesible. Valida con el validador de W3C.
  2. Replica un layout con Grid y Flexbox (una tarjeta, una navbar). Juega Flexbox Froggy y Grid Garden.
  3. Escribe una función JS con closure y otra con reduce para resolver un problema real.
  4. Crea un componente React con estado y efectos que cargue datos de un API y los muestre.
  5. Añade TypeScript a un proyecto JS pequeño y conviértelo.
  6. Escribe tests con Testing Library (comportamiento) para tus componentes.
  7. Corre Lighthouse en tu proyecto y arregla las 3 métricas peores.
  8. Arma un proyecto Next.js con SSG + SSR + una Server Action.

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.