JavaScript te permite hacer casi cualquier cosa. El problema es que «casi cualquier cosa» incluye un montón de opciones que pueden explotar en tus manos en producción, tres semanas después y a una hora complicada. La buena noticia: la mayoría de los tiros en el pie provienen de un puñado de hábitos, y todos tienen solución. Estos son los que más vale la pena corregir.

Jubila var: solo const y let
No hay ningún motivo para usar var en código nuevo. Se escapa del ámbito de la función, sube al principio mediante hoisting y permite reasignar variables sin querer. Usa const por defecto y cambia a let solo cuando REALMENTE vayas a reasignar la variable.
js const taxa = 0.15; let total = 0; for (const item of carrinho) total += item.preco;
Regla práctica: empieza todo como const. Si el linter indica que necesitas reasignar la variable, entonces conviértela en let. var es código de 2014.
=== siempre, == nunca
== hace una coerción de tipos antes de comparar, y sus reglas son un pequeño campo minado. 0 == '' es true. null == undefined es true. [] == false es true. Nadie tiene esa tabla en la cabeza, y no debería ser necesario.
js if (valor === 0) { / predecible / }
Usa siempre === y !==. Si necesitas tratar null y undefined juntos, sé explícito en ese caso puntual o resuélvelo con ??.
async/await en lugar del infierno de callbacks
Un callback anidado dentro de otro callback, dentro de otro callback, es la famosa pirámide de la desgracia. async/await hace que el código asíncrono parezca síncrono, línea tras línea.
js async function carregarPerfil(id) { const user = await buscarUsuario(id); const posts = await buscarPosts(user.id); return { user, posts }; }
¿Necesitas ejecutar varias cosas en paralelo? Usa Promise.all en lugar de hacer await en serie:
js const [user, config] = await Promise.all([buscarUsuario(id), buscarConfig(id)]);
Una promise sin tratamiento es una bomba de tiempo
Un async/await bonito no te salva de los errores. Una promise que se rechaza y nadie captura termina en un unhandledRejection; en Node moderno, eso puede derribar el proceso.
js try { const dados = await buscarDados(); usar(dados); } catch (err) { logger.error('falha ao buscar dados', err); // decide: fallback, re-throw o responder con un error }
Toda operación asíncrona que pueda fallar necesita un try/catch o un .catch(). «Después lo soluciono» es la forma en que nacen los bugs de producción.
ESM e inmutabilidad: menos sorpresas, menos bugs
Usa módulos ESM (import/export), no variables globales ni require repartido por todo el código. Un módulo deja claro qué entra y qué sale, el bundler puede hacer tree-shaking y nada se filtra al ámbito global sin que lo veas.
js // util.js export function slugify(texto) { / ... / } // app.js import { slugify } from './util.js';
Además, evita mutar datos sin necesidad. map, filter y reduce devuelven arrays nuevos en lugar de modificar el original: menos efectos colaterales, menos bugs fantasma.
js const ativos = usuarios.filter(u => u.ativo); const nomes = ativos.map(u => u.nome);
La mutación no está prohibida, pero cuando la necesites, debe ser una decisión consciente, no un accidente.
this no es tu amigo
En JavaScript, this depende de CÓMO se llama a la función, no de dónde se definió. Si pasas un método como callback, this desaparece.
js class Timer { segundos = 0; // la arrow preserva el this de la instancia tick = () => { this.segundos++; }; } setInterval(timer.tick, 1000); // funciona
Las arrow functions no tienen un this propio: lo heredan del ámbito en el que se crean. Por eso resuelven el 90 % de los dolores de cabeza con callbacks. Pero ten cuidado: NO uses una arrow como método cuando dependas de un this dinámico, por ejemplo, en handlers del DOM que esperan que this sea el elemento.
Ninguna de estas prácticas es sofisticada. Son pequeños hábitos que, sumados, transforman «¿por qué se rompió esto?» en código que hace exactamente lo que parece hacer. Y el código predecible es el único tipo de código que puedes mantener a las dos de la mañana.

