Un contador no enseña siete conceptos: revela un ciclo de estado, evento y renderizado

Java

Un contador que muestra el número correcto después de hacer clic parece un ejercicio pequeño. En realidad, obliga a responder cuatro preguntas: quién escucha el evento, dónde vive el estado, cuándo se recalcula el valor y qué parte de la pantalla se actualiza.

La idea útil no es memorizar siete conceptos de JavaScript, sino separar evento, estado, regla y renderizado para que cada decisión pueda probarse. Esa separación también tiene valor fuera del ejercicio: ayuda a explicar un proyecto en un portafolio, revisar un cambio a distancia y localizar un bug sin modificar el DOM al azar.

El contador como mapa de una decisión

Imagina un contador de tareas para una persona que trabaja desde casa: el valor actual puede representar tareas terminadas y el límite una meta del día. Al pulsar “+1”, el evento no debería escribir directamente en varios elementos. Primero solicita una transición; la regla comprueba si es válida; el estado cambia una sola vez; y el renderizado refleja el nuevo estado.

PiezaResponsabilidadPregunta de prueba
EventoComunicar que ocurrió una interacción¿La acción llegó al controlador correcto?
EstadoConservar valor, límite y mensaje¿Existe una fuente de verdad?
ReglaDecidir si la transición es válida¿Qué ocurre en cero y en el límite?
RenderizadoReflejar el estado en la interfaz¿La pantalla coincide con el estado?

Un contador con esta forma puede convertirse en una pequeña pieza de portafolio para mostrar cómo piensas: no solo “hice botones”, sino que definiste estados, límites, mensajes y pruebas. En un trabajo remoto, esa explicación escrita permite que alguien revise el proyecto sin compartir tu pantalla.

El evento comunica intención, no decide el resultado

addEventListener() registra una función para ejecutarse cuando el evento indicado llega a un objetivo. Así lo describe la referencia de MDN sobre EventTarget.addEventListener(). El listener puede traducir un clic o una tecla a una intención, pero no debería contener toda la política del contador.

sumar.addEventListener('click', () => intentar(1));
restar.addEventListener('click', () => intentar(-1));
reiniciar.addEventListener('click', () => reiniciarEstado());

Separar el evento de la regla permite reutilizar la misma transición desde un botón, un atajo de teclado o una prueba automatizada. También hace más fácil cambiar el texto de la interfaz sin alterar la decisión matemática.

HTML semántico reduce decisiones innecesarias

El ejemplo necesita botones reales para sumar, restar y reiniciar, un output para el valor y un mensaje de estado:

<section aria-labelledby="titulo-meta">
  <h2 id="titulo-meta">Meta del día</h2>
  <label for="limite">Límite</label>
  <input id="limite" type="number" min="1" value="5">
  <button id="restar" type="button">−1</button>
  <output id="salida" aria-live="polite" aria-atomic="true">0</output>
  <button id="sumar" type="button">+1</button>
  <progress id="progreso" max="5" value="0"></progress>
  <p id="mensaje" role="status" aria-live="polite"></p>
  <button id="reiniciar" type="button">Restablecer</button>
</section>

Un botón nativo ya expresa una acción y participa en la interacción de teclado. No conviertas un div en botón solo porque puedes añadirle un listener. Para reforzar esta separación, revisa la guía interna sobre responsabilidades de HTML, CSS y JavaScript.

El estado debe ser pequeño y explícito

const estado = {
  valor: 0,
  limite: 5,
  mensaje: '',
  error: null
};

function intentar(delta) {
  const proximo = estado.valor + delta;

  if (proximo < 0) {
    estado.error = 'No puedes bajar de cero.';
    estado.mensaje = estado.error;
    render();
    return;
  }

  if (proximo > estado.limite) {
    estado.error = 'Has llegado al límite.';
    estado.mensaje = estado.error;
    render();
    return;
  }

  estado.valor = proximo;
  estado.error = null;
  estado.mensaje = delta > 0 ? 'Sumaste una tarea.' : 'Restaste una tarea.';
  render();
}

intentar() calcula una posibilidad y la valida antes de modificar el valor. Si la operación no es válida, conserva el estado anterior y actualiza el mensaje. Esa decisión es observable: una prueba puede comprobar que el valor no cambió, que el error apareció y que el botón adecuado quedó desactivado.

Renderizar es reflejar, no adivinar

function render() {
  salida.value = String(estado.valor);
  progreso.max = String(estado.limite);
  progreso.value = String(estado.valor);
  mensaje.textContent = estado.mensaje;

  restar.disabled = estado.valor === 0;
  sumar.disabled = estado.valor === estado.limite;
}

La función de renderizado lee una única fuente de verdad. En cero, “−1” se desactiva; en el límite, “+1” se desactiva; el progreso muestra la relación entre valor y techo. Si otra parte del código cambia el texto sin cambiar el estado, la interfaz puede parecer correcta durante un instante y quedar incoherente después.

En una revisión remota, un diff que contiene una función de transición y una función de renderizado es más fácil de discutir que una secuencia de escrituras dispersas en muchos nodos. La estructura no elimina la necesidad de criterio, pero hace que el criterio tenga un lugar identificable.

Accesibilidad forma parte del ciclo

Prueba una modificación deliberadamente incómoda: cambia el valor inicial, pulsa varias veces y recarga la página. Si el resultado depende de una variable que desaparece o de un listener duplicado, el ejercicio deja de ser una cuenta y se convierte en una investigación del ciclo de la interfaz.

Eso no convierte automáticamente al contador en accesible. Prueba el teclado, conserva un foco comprensible, utiliza controles nativos y verifica que los mensajes no produzcan ruido. Para una alerta crítica puede existir otra prioridad, pero no uses assertive para cada incremento: interrumpir continuamente también perjudica la experiencia.

Cuando una persona trabaja de forma remota con una conexión limitada o con herramientas de asistencia, un estado explícito y un mensaje breve son parte de la calidad del producto, no un adorno. El equipo puede revisar esos criterios en una pull request aunque no comparta el mismo entorno físico.

Un límite que cambia también es un evento

Si el límite puede editarse durante la interacción, define qué sucede cuando el valor actual queda por encima del nuevo máximo. Puedes ajustar el valor, rechazar el límite o marcar un estado inválido. Lo importante es elegir una política y aplicarla en un único lugar, no resolverla de manera distinta en cada listener.

limiteInput.addEventListener('input', () => {
  const nuevoLimite = Number(limiteInput.value);

  if (!Number.isInteger(nuevoLimite) || nuevoLimite < 1) {
    estado.error = 'El límite debe ser un entero positivo.';
    estado.mensaje = estado.error;
    render();
    return;
  }

  estado.limite = nuevoLimite;
  if (estado.valor > estado.limite) {
    estado.valor = estado.limite;
  }
  estado.error = null;
  estado.mensaje = 'Límite actualizado.';
  render();
});

Este caso muestra por qué la entrada del usuario debe convertirse y validarse antes de incorporarse al estado. Para rastrear errores cuando la pantalla no coincide con la intención, puedes consultar la guía interna sobre errores frecuentes de JavaScript.

Persistencia cambia el modelo de la interfaz

La versión en memoria vuelve a cero al recargar. MDN documenta que localStorage conserva datos entre sesiones para el origen, mientras que sessionStorage se mantiene durante la sesión de la página. Elegir uno no es una línea neutra: introduce decisiones sobre limpieza, privacidad, pestañas y recuperación.

const guardado = localStorage.getItem('meta-diaria');
if (guardado !== null) {
  const valor = Number(guardado);
  if (Number.isInteger(valor) && valor >= 0) {
    estado.valor = valor;
  }
}

function guardar() {
  localStorage.setItem('meta-diaria', String(estado.valor));
}

No guardes automáticamente datos sensibles en Web Storage y no asumas que el navegador siempre permitirá persistirlos. Para una meta de tareas, el riesgo puede ser bajo; para información personal o de negocio, necesitas otra arquitectura. En un proyecto remoto, documenta qué queda solo en el navegador y qué debe sincronizarse con un servidor.

Prueba el ciclo, no solo el clic

EscenarioResultado esperado
Valor ceroRestar no cambia el valor y el mensaje explica el límite.
Valor intermedioSumar y restar actualizan estado, salida y progreso.
Valor máximoSumar se desactiva o se rechaza con un mensaje claro.
Límite inválidoEl estado conserva el valor anterior y muestra el error.
TecladoLas acciones funcionan sin ratón y el foco permanece comprensible.
Región vivaEl cambio importante se anuncia sin interrupciones innecesarias.
RecargaEl comportamiento coincide con la decisión sobre persistencia.

Si una prueba falla, vuelve a la cadena: ¿llegó el evento?, ¿la regla calculó el próximo estado?, ¿el estado cambió una sola vez?, ¿el renderizado lo leyó?, ¿el mensaje reflejó la decisión? Este método localiza la capa responsable y facilita que otra persona reproduzca el problema desde una issue o pull request.

Un ejercicio pequeño que comunica una forma de trabajar

Para alguien que busca practicar front-end o preparar un portafolio en México, el contador puede ser útil si se presenta como un producto pequeño y no como una colección de botones. Explica el problema, muestra el estado, incluye pruebas de límites, documenta accesibilidad y deja claro qué ocurre al recargar. Esa evidencia no garantiza un empleo remoto, pero hace visible cómo tomas decisiones y colaboras sobre un cambio concreto.

Si el equipo está distribuido entre ciudades o países, usa una descripción breve del cambio: qué estado se modificó, qué casos se probaron, qué queda fuera y qué decisión necesita revisión. La calidad del código incluye la calidad de la comunicación que permite mantenerlo.

Protocolo final para cualquier interfaz pequeña

  • Define qué evento expresa la intención de la persona.
  • Conserva el estado en una fuente de verdad clara.
  • Aplica reglas antes de modificar el estado.
  • Haz que el renderizado lea el estado y no recuerdos dispersos de eventos.
  • Usa elementos nativos y comunica actualizaciones dinámicas.
  • Decide explícitamente qué se guarda, durante cuánto tiempo y dónde.
  • Prueba límites, errores, teclado, asistencia y recarga.
  • Documenta el resultado para una revisión asíncrona.

Un contador no enseña siete conceptos porque una lista sea mágica. Enseña porque concentra una cadena causal completa: alguien actúa, el navegador entrega un evento, una regla decide si el estado puede cambiar y el renderizado comunica el resultado. Cuando esa cadena está clara, la interfaz puede crecer sin perder el mapa.

Si la cuenta falla, inspecciona el recorrido completo: evento, estado, cálculo y renderizado. El ejercicio termina cuando puedes señalar dónde se perdió la sincronización, no cuando el número vuelve a subir.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio