From n00b to ZeroCool / Origen
JavaScript: el lenguaje que nadie planeó… y aun así sostiene la web
Historia real y guía práctica para dominar JS en React+Vite: coerción, scope, async, módulos y bugs típicos del jale diario.
Lo que vale la pena leer aquí
undefined is not a function
Intro con gancho
Son las 11:47 pm. Tu laptop ya pide jubilación, el ventilador suena como dron y tú nomás querías cerrar el deploy. Abres DevTools y ahí está el madrazo:
undefined is not a function
Y lo peor: hace una hora “sí jalaba”. El jefe cae con el clásico: “es un cambio chiquito, ¿no?” y tú viendo cómo una app entera se te voltea por una línea.
Esa es la relación con JavaScript: improvisado, lleno de decisiones raras… pero es el pegamento de la web. Si vas en la ruta From HTML to Fullstack, JS es el puente entre “puse un botón” y “esto ya es una app con estados, requests, errores y deadlines”.
Qué vas a aprender
- Por qué JavaScript terminó dominando (y cómo su historia se nota en tus bugs).
- 6 trampas que rompen apps reales: coerción,
this, scope, mutación, async, módulos. - Un setup rápido con Vite + React para practicar sin pelearte con tooling viejo.
- Reglas de jale para escribir JS más predecible en frontend.
- Snippets y mini-ejercicios que se sienten como tickets de verdad.
Contexto práctico
Si vienes de HTML/CSS, el primer golpe con JavaScript suele ser uno de estos:
- “¿Por qué esto no truena hasta runtime?”
- “¿Por qué
[] + {}da algo que parece broma?” - “¿Por qué mi
console.logsale raro consetTimeout?”
Y encima está la vida real:
- El PM ya mandó WhatsApp: “urge para mañana, el cliente lo quiere en production”.
- Te pasaron un snippet de StackOverflow del 2017 con la frase maldita: “funciona, nomás no lo muevas”.
- Estás chambeando entre pendientes, con CFE coqueteando con el apagón.
La neta: JS no se domina coleccionando rarezas. Se domina entendiendo cómo toma decisiones y poniéndole barandales a tu workflow.
Paso a paso: domina lo que de verdad te rompe
1) Arranca un playground serio (React + Vite)
Probar en un archivo suelto sirve… hasta que te topas con módulos, bundling y el DOM real. Mejor practica donde vive tu stack hoy.
npm create vite@latest js-real-life -- --template react
cd js-real-life
npm install
npm run dev
Abre src/App.jsx y deja un área para hacer pruebas sin ensuciar todo.
export default function App() {
return (
<main style={{ padding: 16, fontFamily: 'system-ui' }}>
<h1>JS Real Life</h1>
<p>Abre la consola. Aquí se rompen cosas con dignidad.</p>
</main>
);
}
Decisión práctica: si tu código vive en módulos ES dentro de un bundler moderno, tu práctica también.
2) Coerción: cuando JS “te ayuda” sin pedir permiso
JS convierte tipos automáticamente. A veces te salva. A veces te mete un bug silencioso que aparece justo cuando ya hiciste deploy.
Pruébalo en consola:
console.log('5' + 1); // '51'
console.log('5' - 1); // 4
console.log(false == 0); // true
console.log(false === 0); // false
Regla de jale: usa === casi siempre. Deja == para casos raros y bien justificados (y ojalá comentados).
Caso típico de UI (inputs):
const quantity = '2';
const price = 199;
// Bug común:
const total = quantity * price; // 398 (aquí “funciona”)
const label = 'Total: $' + total; // 'Total: $398'
// Pero si alguien cambia * por +:
const broken = quantity + price; // '2199'
Decisión práctica: convierte desde la entrada. No esperes “a ver si pega”.
const quantityNumber = Number(quantity);
if (Number.isNaN(quantityNumber)) throw new Error('Cantidad inválida');
3) let, const y el scope: defensa contra bugs fantasmas
var tiene scope de función y hoisting que se presta a confusión. En frontend moderno, usarlo es comprarte talacha gratis.
Ejemplo clásico que luego se vuelve ticket:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var i:', i), 0);
}
// imprime 3, 3, 3
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let j:', j), 0);
}
// imprime 0, 1, 2
Regla de jale:
constpor default.letsolo si reasignas.varsolo si estás tocando legacy (y con cuidado, porque luego nadie lo “rollbackea”).
4) Objetos/arrays: mutación silenciosa y “¿por qué se movió todo?”
En React esto pega durísimo. Mutar estado puede dejarte UI que no re-renderiza o, peor, inconsistencias que no se ven hasta que alguien hace click en el flujo raro.
const user = { name: 'Ana', role: 'admin' };
const alias = user;
alias.role = 'viewer';
console.log(user.role); // 'viewer' (sorpresa)
Regla de jale: si quieres copiar, copia.
const copy = { ...user, role: 'viewer' };
Con arrays:
const items = [1, 2, 3];
const next = items.map(x => x * 2); // no muta
items.push(4); // muta
Decisión práctica en React: evita push, splice, mutaciones directas. Prefiere map, filter, toSorted (si tu target lo soporta) o copias con spread.
5) this: el villano de los callbacks (y por qué casi ya no lo usas)
this depende de cómo llamas a la función, no de dónde está escrita. Y eso rompe cuando pasas funciones como callbacks.
const obj = {
name: 'Byte',
say() {
return `Hola ${this.name}`;
}
};
const fn = obj.say;
console.log(fn()); // 'Hola undefined' (en strict mode)
Soluciones reales:
- Arrow functions para callbacks.
.bind(obj)cuando de plano lo necesitas.
const safe = obj.say.bind(obj);
console.log(safe()); // 'Hola Byte'
Decisión práctica: en React funcional casi no necesitas this. Si te está dando guerra, probablemente tu diseño pide refactor, no hacks.
6) Async: promesas, microtareas y el bug de “ya quedó… ah no”
Lo más común: olvidar await o asumir que todo corre en orden “como se ve”.
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function run() {
console.log('A');
wait(10); // bug: no esperaste
console.log('B');
}
run();
Arreglado:
async function run() {
console.log('A');
await wait(10);
console.log('B');
}
Caso real en UI (fetch + estado):
async function loadUser() {
const res = await fetch('/api/user');
if (!res.ok) throw new Error('Falló la carga');
return res.json();
}
Decisión práctica: maneja error y loading aunque sea básico.
loadingpara evitar doble click.try/catchpara darle feedback a la banda.
7) Módulos ES: deja de vivir en el caos del global
Antes todo vivía en global. Hoy, con Vite, casi todo es módulo. Si algo “no existe”, muchas veces es porque no lo importaste.
// utils/math.js
export const sum = (a, b) => a + b;
// App.jsx
import { sum } from './utils/math';
console.log(sum(2, 3));
Regla de jale: si ves referencias “mágicas”, sospecha. Imports explícitos te salvan cuando llega el refactor de última hora.

Screenshots sugeridos
- Consola del navegador con ejemplos de coerción (
'5'+1vs'5'-1). - DevTools mostrando el call stack de un error típico (
undefined is not a function). - Vite corriendo en
localhosty la estructura de carpetas (src,main.jsx,App.jsx). - Un ejemplo de React re-render: antes/después de mutar vs copiar un array.
Errores comunes + solución
Error 1: “Mi if entra cuando no debería”
Síntoma: validaciones raras con strings vacíos, 0, null.
if ('0') console.log('entra'); // entra
if (0) console.log('entra'); // no entra
Solución: define reglas explícitas.
const value = '0';
const hasValue = value !== '' && value !== null && value !== undefined;
Error 2: “Cambié el estado pero React no actualiza”
Síntoma: UI no se refresca.
Causa típica: mutación directa.
Solución: crea una copia.
setItems(prev => prev.map(x => x.id === id ? { ...x, done: true } : x));
Error 3: “Me marca CORS / fetch falla y ni sé por qué”
Síntoma: TypeError: Failed to fetch.
Solución práctica (frontend):
- Revisa la pestaña Network.
- Verifica URL y método.
- En dev, usa proxy si aplica (cuando ya haya backend). Si no hay backend, mockea.
const data = await Promise.resolve({ name: 'Mock' });
Error 4: “map truena: cannot read properties of undefined”
Síntoma: users.map cuando users todavía no llega.
Solución: default seguro.
const usersSafe = users ?? [];
return usersSafe.map(u => u.name);
Error 5: “No entiendo por qué mi console.log sale desordenado”
Causa: async + event loop.
Solución: marca el flujo y usa await.
console.log('antes');
await doThing();
console.log('después');

Checklist final
- Uso
===y convierto tipos desde la entrada (inputs, query params). -
constpor default;letsolo si reasigno; evitovar. - No muté arrays/objetos que viven en estado; uso copias (
...,map,filter). - En async, cada promesa importante tiene
await(o se maneja con.then/.catch). - Mis funciones no dependen de
thissalvo casos justificados. - Todo lo que uso está importado (módulos ES), cero globals mágicos.
- Puedo explicar qué hace mi código con 2 logs bien puestos (sin rezarle al debugger).
FAQ
1) ¿Por qué dicen que JavaScript “nadie lo planeó”?
Porque se armó rápido para el navegador, con decisiones a contrarreloj y compatibilidad como prioridad. Luego crecieron frameworks, módulos y tooling encima… sin poder romper la web que ya existía.
2) ¿Entonces JavaScript es “malo”?
No. Es un lenguaje con historia pesada y tradeoffs raros. Si le pones barandales (buenas prácticas, linting, tests), te deja ser bien productivo.
3) ¿Qué me conviene primero: aprender JS “vanilla” o irme directo a React?
Un básico sólido de JS (tipos, funciones, arrays, objetos, async) te ahorra sufrimiento en React. Pero se vale aprenderlos en paralelo: montas mini componentes, rompes cosas y entiendes por qué.
4) ¿Qué parte de JS pega más en frontend?
Async (fetch y estados de carga), mutación (estado/UI) y coerción (inputs). De ahí salen muchos bugs “misteriosos”.
5) ¿TypeScript arregla esto?
Ayuda muchísimo (tipos, autocomplete, refactors). Pero no reemplaza entender coerción, scope, mutación y async. Si no entiendes JS, TS solo te grita con mejores mensajes.
Siguiente episodio: teaser
Ya con JS más domesticado, toca usarlo para UI de verdad.
Seguimos con React en Vite: componentes, estado y el primer workflow que aguanta cambios sin romperse cada viernes.
Idea para cerrar bien este post: toma una sola práctica de aquí y conviértela en algo que tu equipo pueda aplicar hoy.
Cuando un artículo aterriza en decisiones reales, deja de ser contenido y se vuelve ventaja.


