🎨 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 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
h1por página; saltarse niveles (h1→h3) 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">
⚠️
labelconfor: 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 ysetTimeoutclásicos pierden elthis— 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
awaitno 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
propso sustate. 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:
useEffectes 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
- HTML semántico (h1-h6, nav, main, article) → el lector de pantalla lo entiende gratis.
- Labels en inputs (
label for). - Texto alternativo en imágenes (
alt). - Contraste suficiente (WCAG AA: 4.5:1 en texto normal).
- Navegación por teclado: todo debe ser operable sin ratón (
tab,focusvisible). - 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
- Construye una página semántica con header, nav, main, article, footer y un formulario accesible. Valida con el validador de W3C.
- Replica un layout con Grid y Flexbox (una tarjeta, una navbar). Juega Flexbox Froggy y Grid Garden.
- Escribe una función JS con closure y otra con
reducepara resolver un problema real. - Crea un componente React con estado y efectos que cargue datos de un API y los muestre.
- Añade TypeScript a un proyecto JS pequeño y conviértelo.
- Escribe tests con Testing Library (comportamiento) para tus componentes.
- Corre Lighthouse en tu proyecto y arregla las 3 métricas peores.
- Arma un proyecto Next.js con SSG + SSR + una Server Action.
Para profundizar
- MDN Web Docs: la biblia de la plataforma web, en español.
- web.dev — Learn CSS / Performance / Accessibility: los cursos de Google.
- javascript.info (es): el curso JS completo en español.
- The Odin Project: fundamentos con proyectos guiados.
- react.dev: React de la fuente de la verdad.
- Full Stack Open (es): el MOOC universitario gratuito de React + Node.
- CSS moderno, Event loop JS: wikis del sitio.
- Sigue con 👑 Nivel Élite o vuelve al 🗺️ Plan.