El desarrollo web ha evolucionado enormemente en los últimos años. Esta guía cubre los fundamentos que todo desarrollador debería dominar en 2025: HTML semántico, CSS moderno, JavaScript actualizado y el consumo correcto de APIs REST.
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:
<div class="header">
<div class="nav">...</div>
</div>
<div class="main">
<div class="sidebar">...</div>
<div class="content">...</div>
</div>
<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.
:root {
--color-primary: #2563eb;
--color-text: #1f2937;
--color-bg: #f9fafb;
--spacing-base: 1rem;
--radius: 0.75rem;
--font-body: 'Inter', system-ui, sans-serif;
}
.card-grid {
display: grid;
grid-template-columns: repeat(
auto-fill, minmax(280px, 1fr)
);
gap: var(--spacing-base);
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.5rem;
flex-wrap: wrap;
}
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { display: grid; grid-template-columns: auto 1fr; }
}
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:
const ciudad = usuario?.direccion?.ciudad ?? 'Ciudad desconocida';
const { nombre = 'Anónimo', edad = 0, rol = 'user' } = usuario;
const activos = usuarios
.filter(u => u.activo)
.map(u => ({ id: u.id, nombre: u.nombre }))
.sort((a, b) => a.nombre.localeCompare(b.nombre));
const porRol = Object.groupBy(usuarios, u => u.rol);
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);
});
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:
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' });
}
}
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:
async function safeAwait(promise) {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error, null];
}
}
async function cargarDashboard(userId) {
const [errUser, usuario] = await safeAwait(api.get(`/users/${userId}`));
if (errUser) {
mostrarError('No se pudo cargar el usuario');
return;
}
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 });
}
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());
}
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.
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preload" href="hero-image.webp" as="image" fetchpriority="high">
<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"
>
<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
│ │ ├── components.css
│ │ └── layout.css
│ ├── js/
│ │ ├── api-client.js
│ │ ├── components.js
│ │ └── main.js
│ └── 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.