Una tarjeta de producto parece una unidad pequeña: una imagen, un título, un precio y un botón. Entonces llega una petición aparentemente menor: “que se pueda guardar como favorita, que muestre una advertencia si queda poco stock y que el formulario no permita un correo inválido”. En ese instante se descubre por qué HTML, CSS y JavaScript no son tres turnos que se estudian en orden. Son tres fronteras que debes conservar si quieres cambiar una parte sin romper otra.
El error de principiante no es mezclar tecnologías por ignorancia. Es resolver la primera versión visible del problema en la capa que está más a mano: añadir onclick al HTML, ocultar un error con CSS o pedirle a JavaScript que describa una relación que el documento ya podía declarar. Funciona durante cinco minutos. Después nadie sabe dónde vive cada decisión.
La tarjeta antes de tener comportamiento
Empieza por declarar qué existe. La referencia de HTML de MDN define HTML como la tecnología que aporta significado y estructura al contenido. No habla de “hacer una caja bonita”; habla de decir que hay un artículo, un título, un precio, una imagen, una lista o un botón.
<article class="producto">
<img src="teclado.jpg" alt="Teclado mecánico compacto">
<h2>Teclado mecánico compacto</h2>
<p>89 €</p>
<button type="button">Guardar como favorito</button>
</article>Esta versión ya tiene información útil sin CSS ni JavaScript. El navegador reconoce un botón; una persona que navega con teclado puede enfocarlo; una tecnología asistiva encuentra un encabezado y una imagen con texto alternativo. El trabajo de HTML es responder: ¿qué es cada parte y cómo se relaciona?
Esto no vuelve a HTML “más importante” que las otras capas. Le asigna una responsabilidad que no debería depender de un clic para existir. La guía de etiquetas HTML y semántica profundiza en cómo elegir elementos por significado antes de usar un contenedor genérico.
¿Entonces nunca debo usar div?
Usa div cuando necesitas agrupar contenido sin que el grupo tenga un significado más preciso. No es una etiqueta incorrecta; es una etiqueta neutral. El problema aparece cuando un bloque ya es navegación, artículo, formulario, botón o lista y se pierde ese significado solo para hacer más fácil un selector CSS.
La misma tarjeta necesita aparecer de otra manera
Ahora el equipo pide que los productos con poco stock se vean distintos. Esa petición no cambia qué es el producto ni qué evento ocurrió. Cambia cómo se presenta una condición. CSS es la capa que describe esa presentación en pantalla, papel, voz u otros medios, como explica MDN CSS.
.producto {
display: grid;
gap: 0.75rem;
padding: 1rem;
}
.producto--stock-bajo {
border-left: 4px solid #c25b1d;
}
.aviso-stock {
color: #8a2c00;
font-weight: 700;
}CSS puede hacer que la advertencia sea visible, ajustar la jerarquía visual o reorganizar la tarjeta en una pantalla estrecha. Pero no debería decidir por sí mismo que hay poco stock. Si un color es la única señal, una persona que no ve ese color recibe menos información. El HTML debe contener el aviso; CSS puede convertirlo en una señal visual más clara.
Este límite también explica por qué el box model importa: tamaño, padding, borde y layout son decisiones de presentación. No deberían alterar el nombre del producto, borrar su significado semántico ni convertirse en el lugar donde se calcula una regla de negocio.
¿Puedo ocultar elementos con CSS?
Sí, cuando el estado y el significado ya existen y solo decides si algo se muestra en una condición concreta. Antes de hacerlo, pregunta si el elemento debe seguir disponible para lectores de pantalla, si el usuario podrá recuperarlo y si ocultarlo cambia el orden lógico del contenido. “No se ve” y “no existe para quien usa la página” no son la misma decisión.
La petición cambia: ahora hay un evento y un estado
Guardar un favorito no es presentación. Requiere escuchar una acción, cambiar un dato y reflejar ese cambio. JavaScript entra aquí porque aporta comportamiento: escucha eventos, modifica datos y coordina APIs del navegador. MDN JavaScript aclara que el lenguaje también vive fuera de la web; DOM y Web APIs son las capacidades del entorno que permiten trabajar con una página.
const boton = document.querySelector("[data-favorito]");
boton.addEventListener("click", () => {
const estabaGuardado = boton.getAttribute("aria-pressed") === "true";
boton.setAttribute("aria-pressed", String(!estabaGuardado));
boton.textContent = estabaGuardado ? "Guardar como favorito" : "Guardado";
});Observa el reparto. HTML declara que hay un botón; CSS puede indicar visualmente que está guardado; JavaScript reacciona al clic y actualiza el estado. Si después cambias el diseño de una tarjeta a una lista, no deberías reescribir la lógica de favoritos. Si cambias la lógica para guardar en un servidor, no deberías perder el significado de encabezados, botones y mensajes.
El artículo sobre manipulación del DOM muestra métodos y eventos. El criterio aquí es anterior al método: antes de usar querySelector o classList, decide qué estado existe y qué capa debe expresarlo.
Un cambio atraviesa las tres capas sin mezclarlas
Volvamos a la petición inicial. “Advertir poco stock” puede requerir las tres capas, pero cada una hace una cosa distinta:
| Pregunta de la petición | Capa responsable | Decisión concreta |
|---|---|---|
| ¿Qué mensaje existe? | HTML | Un texto que indica unidades restantes, relacionado con el producto. |
| ¿Cómo atrae atención sin depender solo del color? | CSS | Jerarquía, contraste, borde y espacio que acompañan al texto. |
| ¿Cuándo se actualiza? | JavaScript | Una petición o evento que recibe el nuevo stock y actualiza el estado. |
Esta tabla no prohíbe que una capa toque a otra. JavaScript puede insertar contenido, CSS puede responder a un atributo y HTML puede cargar un script. La frontera útil no es una muralla técnica: es una pregunta sobre responsabilidad. Cuando cambie el requisito, ¿dónde buscaría una persona razonable la decisión que lo implementa?
¿Por qué no poner onclick directamente en el HTML?
Puede funcionar en un ejemplo corto, pero dispersa comportamiento entre nodos de contenido. Cuando tienes varios botones o necesitas cambiar la condición, encontrar el código exige revisar markup y lógica a la vez. Un listener centralizado mantiene el HTML enfocado en qué control existe y el JavaScript en qué sucede al activarlo.
El formulario revela otro límite útil
Ahora la tarjeta incorpora un formulario para avisar cuando vuelva el stock. HTML ya puede declarar que el campo es correo y que es obligatorio:
<label for="correo">Avísame cuando haya stock</label>
<input id="correo" name="correo" type="email" required>
<button>Suscribirme</button>Esto proporciona validación nativa básica y relación entre etiqueta y control. CSS puede mostrar el estado inválido. JavaScript puede ofrecer mensajes adicionales, enviar la solicitud o mostrar una respuesta del servidor. Pero si JavaScript falla, el campo sigue siendo un correo requerido; si CSS no carga, la etiqueta sigue explicando el control. Esa degradación no es una desventaja: es el resultado de dar a cada capa una responsabilidad que puede sostener.
¿JavaScript puede crear toda la interfaz?
Puede, y muchas aplicaciones lo hacen mediante frameworks. La pregunta no es si es técnicamente posible, sino si el resultado conserva semántica, estados accesibles y una frontera comprensible. Aunque el markup se genere dinámicamente, alguien debe seguir decidiendo qué significa cada nodo, qué estilo expresa cada estado y qué comportamiento cambia los datos.
Antes de añadir código, formula la pregunta correcta
Cuando una interfaz pida un cambio, prueba este orden: ¿cambia lo que la persona puede encontrar o entender? Empieza por HTML. ¿Cambia cómo se presenta algo que ya existe? Mira CSS. ¿Cambia por una acción, dato, tiempo o respuesta remota? Diseña el comportamiento con JavaScript. A veces la respuesta es una capa; otras, las tres. El valor está en no esconder las tres decisiones en el primer archivo disponible.
HTML, CSS y JavaScript se aprenden juntos porque una página real los reúne. Pero mantener sus fronteras no es una ceremonia académica. Es lo que permite que tu próximo cambio sea local, que un error tenga una pista clara y que una persona nueva pueda entender qué parte del sistema está mirando.
El estado debe tener un nombre antes de tener una animación
Una interfaz se complica cuando empiezas por los efectos. “El corazón debe ponerse rojo”, “el botón debe tener sombra” y “el mensaje debe entrar con una transición” describen una apariencia, pero no dicen qué estado cambió. ¿El producto fue guardado localmente? ¿Se envió una solicitud? ¿La acción falló y debe volver atrás? La respuesta pertenece al comportamiento y a los datos antes de llegar al CSS.
<button data-favorito aria-pressed="false">
Guardar como favorito
</button>
[aria-pressed="true"] {
background: #1f5c45;
color: white;
}El atributo no crea la lógica de favorito. Pero permite que HTML comunique estado accesible, que JavaScript lo actualice y que CSS lo presente sin depender de un selector frágil basado en posición. Si el diseño cambia de icono, el estado conserva su nombre; si el servidor responde con error, JavaScript puede restaurarlo y añadir un mensaje existente en el documento.
¿Una clase CSS puede ser el estado de la aplicación?
Puede reflejarlo, pero no debería ser el único lugar donde ese estado vive. Si la única evidencia de “guardado” es una clase visual, será difícil sincronizarla con respuesta del servidor, prueba o lectura accesible. Guarda el hecho en una variable, dato o atributo con significado y usa CSS para expresar cómo se ve.
La frontera se prueba con una pregunta de cambio
Imagina cuatro cambios. Si cambia el idioma, ¿puedes actualizar textos sin tocar listeners? Si cambia el diseño, ¿puedes modificar CSS sin reescribir validación? Si la API agrega una condición de stock, ¿puedes ajustar lógica sin convertir encabezados en div? Si alguien navega con teclado, ¿la acción y el mensaje siguen existiendo?
Estas preguntas son más útiles que preguntar si los archivos están separados. En un componente moderno, markup, estilos y comportamiento pueden estar próximos. Lo importante es que cada decisión tenga una ruta identificable y sus efectos no dependan de suposiciones silenciosas. Separar responsabilidades reduce el radio de una modificación: una corrección de contraste no debería romper la suscripción.
El artículo sobre formularios HTML continúa este razonamiento: etiqueta, tipo de campo y validación nativa establecen un contrato desde el documento; la lógica adicional puede construirse encima en lugar de sustituirlo.
Una reparación pequeña no debería cambiar el idioma del documento
Esta idea se vuelve especialmente visible en mantenimiento. Imagina que una revisión detecta que el aviso de stock tiene poco contraste. La reparación debería vivir en CSS: ajustar color, peso, fondo o espacio sin modificar la frase que comunica el número de unidades, el evento que actualiza ese número o el atributo que representa el estado. Si tocar contraste obliga a editar cinco listeners, la presentación absorbió una responsabilidad que no le correspondía.
O imagina que el negocio decide que “poco stock” significa tres unidades en lugar de cinco. Esa es una regla de datos y comportamiento. HTML puede seguir mostrando el aviso; CSS puede seguir destacándolo. La condición cambia en un punto que puede probarse con valores de entrada. Esta localización reduce el riesgo de que una mejora aparente —un nuevo color, una animación, un copy distinto— altere accidentalmente la regla que decide qué ve una persona.
La separación no hace que el proyecto tenga más archivos por deporte. Hace que una petición pueda traducirse en preguntas trazables: qué contenido existe, qué estado es verdadero y cómo debe percibirse. Cuando la siguiente persona cambie una de esas respuestas, no tendrá que reconstruir todo el componente para saber dónde empezar.

Martin Rojas escribe sobre tecnología, programación y desarrollo web. En Skydutz Academy comparte explicaciones prácticas sobre herramientas digitales, conceptos de programación y recursos para aprender de forma progresiva. Su objetivo es hacer que los temas técnicos sean más claros, útiles y accesibles para lectores de distintos niveles.
