1. HTML5 semántico: más que etiquetas

El HTML semántico no es simplemente usar las etiquetas "correctas". Es comunicar el significado y la estructura del contenido tanto a los navegadores como a los motores de búsqueda y las tecnologías de asistencia. Un documento HTML bien estructurado es más accesible, mejor indexado y más fácil de mantener.

El error más común es abusar del elemento <div> cuando existen etiquetas más descriptivas. Aquí la estructura semántica correcta de una página moderna:

<!-- ❌ Mal: estructura sin semántica --> <div class="header"> <div class="nav">...</div> </div> <div class="main"> <div class="sidebar">...</div> <div class="content">...</div> </div> <!-- ✅ Bien: estructura semántica --> <header> <nav aria-label="Navegación principal"> <ul role="list"> <li><a href="/">Inicio</a></li> </ul> </nav> </header> <main> <aside aria-label="Artículos relacionados">...</aside> <article itemscope itemtype="https://schema.org/Article"> <h1 itemprop="headline">Título del artículo</h1> <section>...</section> </article> </main> <footer> <address>Contacto: info@sitio.com</address> </footer>

La estructura semántica también impacta directamente en el SEO. Los motores de búsqueda entienden mejor el contenido cuando las etiquetas comunicar su propósito. El atributo aria-label es crucial para accesibilidad y los atributos itemscope/itemprop implementan Schema.org para rich results en Google.

Jerarquía de encabezados

Una de las reglas más importantes y más frecuentemente violadas: cada página debe tener exactamente un <h1> que describa el tema principal. Los <h2> son subsecciones del H1, los <h3> son subsecciones de los H2, y así sucesivamente. Nunca saltes niveles (de H1 a H3 sin H2).

2. CSS moderno: Grid, Flexbox y Custom Properties

El CSS moderno elimina la necesidad de frameworks pesados para la mayoría de layouts. Grid y Flexbox juntos cubren prácticamente cualquier necesidad de maquetación, y las Custom Properties (variables CSS) permiten temas y consistencia sin preprocesadores.

/* Variables CSS (Custom Properties) */ :root { --color-primary: #2563eb; --color-text: #1f2937; --color-bg: #f9fafb; --spacing-base: 1rem; --radius: 0.75rem; --font-body: 'Inter', system-ui, sans-serif; } /* Grid responsive sin media queries */ .card-grid { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: var(--spacing-base); } /* Flexbox para alineación */ .nav { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; } /* Container queries (CSS moderno 2024) */ .card-wrapper { container-type: inline-size; } @container (min-width: 400px) { .card { display: grid; grid-template-columns: auto 1fr; } } /* :has() selector — CSS moderno */ form:has(input:invalid) .submit-btn { opacity: 0.5; pointer-events: none; }

💡 Grid vs Flexbox: Usa Grid para layouts bidimensionales (filas Y columnas al mismo tiempo). Usa Flexbox para distribuciones en una sola dirección (barra de navegación, botones en fila, items centrados). No hay uno mejor que el otro, son complementarios.

3. JavaScript ES2024: lo esencial actualizado

JavaScript moderno (ES2020+) tiene características que hacen el código más limpio, seguro y expresivo. Aquí los patrones que más impacto tienen en el código diario:

// Optional chaining: evita "Cannot read property of undefined" const ciudad = usuario?.direccion?.ciudad ?? 'Ciudad desconocida'; // Desestructuración con valores por defecto const { nombre = 'Anónimo', edad = 0, rol = 'user' } = usuario; // Array methods modernos const activos = usuarios .filter(u => u.activo) .map(u => ({ id: u.id, nombre: u.nombre })) .sort((a, b) => a.nombre.localeCompare(b.nombre)); // Object.groupBy (ES2024) — agrupar array por clave const porRol = Object.groupBy(usuarios, u => u.rol); // { admin: [...], user: [...], moderador: [...] } // Promise.allSettled — ejecutar en paralelo sin fallar si una rechaza const resultados = await Promise.allSettled([ fetchUsuarios(), fetchProductos(), fetchConfiguracion() ]); resultados.forEach(r => { if (r.status === 'fulfilled') console.log('OK:', r.value); else console.error('Falló:', r.reason); }); // Structuring clone — copia profunda nativa (sin JSON.parse/stringify) const copiaDeep = structuredClone(objetoComplejo);

4. Consumo de APIs REST con Fetch

La API Fetch nativa ha reemplazado a XMLHttpRequest y a la mayoría de librerías de terceros para peticiones HTTP. Con un pequeño wrapper, obtienes toda la funcionalidad necesaria sin dependencias externas:

// api-client.js — cliente HTTP reutilizable class ApiClient { #baseUrl; #defaultHeaders; constructor(baseUrl, options = {}) { this.#baseUrl = baseUrl.replace(/\/$/, ''); this.#defaultHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json', ...options.headers }; } async #request(endpoint, options = {}) { const url = `${this.#baseUrl}/${endpoint.replace(/^\//, '')}`; const config = { headers: { ...this.#defaultHeaders, ...options.headers }, ...options }; const response = await fetch(url, config); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.message ?? `HTTP ${response.status}`); } if (response.status === 204) return null; return response.json(); } get(endpoint, params) { const query = params ? '?' + new URLSearchParams(params) : ''; return this.#request(endpoint + query); } post(endpoint, body) { return this.#request(endpoint, { method: 'POST', body: JSON.stringify(body) }); } put(endpoint, body) { return this.#request(endpoint, { method: 'PUT', body: JSON.stringify(body) }); } delete(endpoint) { return this.#request(endpoint, { method: 'DELETE' }); } } // Uso const api = new ApiClient('https://api.ejemplo.com/v1'); const usuarios = await api.get('/usuarios', { page: 1, limit: 20 });

5. Async/Await y manejo de errores

El manejo correcto de errores asíncronos es donde muchos proyectos fallan silenciosamente. El patrón correcto combina try/catch con una capa de utilidades para evitar repetición de código:

// Patrón "safe-await" para evitar try/catch repetitivos async function safeAwait(promise) { try { const data = await promise; return [null, data]; } catch (error) { return [error, null]; } } // Uso limpio sin try/catch anidados async function cargarDashboard(userId) { const [errUser, usuario] = await safeAwait(api.get(`/users/${userId}`)); if (errUser) { mostrarError('No se pudo cargar el usuario'); return; } // Cargar datos en paralelo const [errData, [pedidos, notificaciones]] = await safeAwait( Promise.all([ api.get(`/users/${userId}/orders`), api.get(`/users/${userId}/notifications`) ]) ); if (errData) { mostrarError('Error cargando datos del dashboard'); return; } renderDashboard({ usuario, pedidos, notificaciones }); } // AbortController: cancelar peticiones function buscarConDebounce(query) { if (window._searchController) window._searchController.abort(); window._searchController = new AbortController(); return fetch(`/api/search?q=${query}`, { signal: window._searchController.signal }).then(r => r.json()); }

6. Rendimiento: Core Web Vitals

Google mide la experiencia de usuario con las Core Web Vitals (CWV). Estas métricas impactan directamente en el ranking de búsqueda. Los tres indicadores clave son:

  • LCP (Largest Contentful Paint): Tiempo hasta que el elemento principal visible carga. Objetivo: < 2.5 segundos.
  • INP (Interaction to Next Paint): Tiempo de respuesta a interacciones del usuario. Objetivo: < 200ms.
  • CLS (Cumulative Layout Shift): Estabilidad visual (que los elementos no salten). Objetivo: < 0.1.
<!-- Optimizaciones de rendimiento en HTML --> <!-- 1. Preconnect a dominios externos --> <link rel="preconnect" href="https://fonts.googleapis.com"> <!-- 2. Preload de recursos críticos --> <link rel="preload" href="hero-image.webp" as="image" fetchpriority="high"> <!-- 3. Imágenes responsive con lazy loading --> <img src="imagen.webp" srcset="imagen-480.webp 480w, imagen-800.webp 800w" sizes="(max-width: 600px) 480px, 800px" width="800" height="450" loading="lazy" decoding="async" alt="Descripción detallada de la imagen" > <!-- 4. Scripts no bloqueantes --> <script src="app.js" defer></script> <script src="analytics.js" async></script>

7. Estructura de proyecto escalable

Para proyectos que crecen, una buena estructura de archivos marca la diferencia. Aquí el patrón que recomendamos para proyectos de tamaño medio:

proyecto/ ├── index.html ├── about.html ├── assets/ │ ├── css/ │ │ ├── base.css # Reset, variables, tipografía │ │ ├── components.css # Botones, cards, formularios │ │ └── layout.css # Grid, nav, footer │ ├── js/ │ │ ├── api-client.js # Cliente HTTP │ │ ├── components.js # Componentes UI │ │ └── main.js # Punto de entrada │ └── images/ │ └── (webp, avif) ├── articles/ │ └── (archivos .html de artículos) ├── robots.txt └── sitemap.xml

8. Conclusión y próximos pasos

El desarrollo web moderno es más potente que nunca sin necesidad de frameworks complejos para proyectos de tamaño pequeño y medio. HTML semántico, CSS Grid/Flexbox y JavaScript moderno con Fetch cubren la enorme mayoría de casos de uso.

Los próximos pasos naturales para profundizar son: explorar Web Components para componentes reutilizables sin framework, aprender Service Workers para aplicaciones offline y profundizar en accesibilidad web (WCAG 2.2), que es tanto una buena práctica como un factor de SEO.