¿Qué es JavaScript?
El único lenguaje de programación nativo de los navegadores web. Hoy también corre en servidores (Node.js), móviles, escritorio y microcontroladores.
JavaScript nació en 1995 para dar interactividad a páginas web. Hoy es el lenguaje más usado del mundo y la base de todo el stack que construimos en VLIM: React en el frontend, Node.js y Moleculer en el backend, BullMQ para colas. Todo es JavaScript.
// Tu primer programa JavaScript console.log("Hola, mundo"); // Variables y operaciones básicas const nombre = "VLIM"; const año = 2024; console.log(`Stack moderno desde ${año} — ${nombre}`); // → "Stack moderno desde 2024 — VLIM" // JavaScript puede hacer aritmética, strings, lógica… const kilos = 2.5; const precio = 189; console.log(`Total: $${kilos * precio}`); // → "Total: $472.5"
Tipos de Datos
JavaScript tiene 8 tipos de datos. Los 7 primitivos son inmutables. El tipo Object es todo lo demás: arrays, funciones, fechas, mapas.
| Tipo | Ejemplo | typeof | Notas |
|---|---|---|---|
| string | "hola" `texto` | "string" | Texto. Las template literals con ` permiten interpolación. |
| number | 42 3.14 NaN Infinity | "number" | IEEE 754 de 64 bits. NaN es "Not a Number" pero typeof NaN === "number" 😅 |
| bigint | 9007199254740993n | "bigint" | Enteros arbitrariamente grandes. Usar para IDs de BD muy grandes. |
| boolean | true false | "boolean" | Resultado de comparaciones y condiciones. |
| undefined | undefined | "undefined" | Variable declarada pero sin valor asignado. |
| null | null | "object" ⚠️ | Ausencia intencional de valor. Bug histórico: typeof null === "object". |
| symbol | Symbol("id") | "symbol" | Identificadores únicos. Usado para propiedades privadas. |
| object | {} [] new Date() | "object" | Colección de propiedades clave-valor. Arrays y funciones son objetos. |
// Primitivos const texto = "Arrachera Premium"; const precio = 189.50; const stock = 42n; // bigint const activo = true; const vacio = null; // sin valor asignado let id; // undefined console.log(typeof texto); // "string" console.log(typeof precio); // "number" console.log(typeof activo); // "boolean" console.log(typeof vacio); // "object" ← bug histórico de JS console.log(typeof id); // "undefined" // Verificación segura de null console.log(vacio === null); // true ← usa ===, no typeof // Coerción implícita (JS intenta convertir tipos) console.log("5" + 3); // "53" ← string gana console.log("5" - 3); // 2 ← "-" no concatena console.log("5" == 5); // true ← igualdad débil (evitar) console.log("5" === 5); // false ← igualdad estricta (usar siempre)
0 == false es true, "" == false es true, null == undefined es true. Con === todos estos son false. La única excepción válida de == en producción es valor == null para detectar tanto null como undefined en un solo check.
Variables y Scope
Tres palabras clave para declarar variables: const, let y var. En código moderno usa const por defecto, let cuando necesites reasignar, y olvídate de var.
// ── const: valor no se puede reasignar ─────────────── const PI = 3.14159; const MAX_STOCK = 1000; // PI = 3; ← TypeError: Assignment to constant variable // OJO: const no hace el objeto inmutable, solo la referencia const producto = { nombre: 'Arrachera', precio: 189 }; producto.precio = 195; // ✅ OK — la referencia no cambió // producto = {}; ← TypeError — reasignación no permitida // ── let: reasignable, scope de bloque ──────────────── let contador = 0; contador = contador + 1; // ✅ OK contador++; // ✅ OK // ── var: scope de función (EVITAR en código moderno) ─ var viejo = 'no usar'; // hoisting, scope confuso // ── Regla de oro: ───────────────────────────────────── // 1. const por defecto // 2. let si necesitas reasignar (contadores, acumuladores) // 3. Nunca var
Operadores Esenciales
Aritméticos, de comparación, lógicos y los modernos operadores de coalescencia y encadenamiento opcional.
// ── Aritméticos ─────────────────────────────────────── console.log(10 % 3); // 1 (módulo / resto) console.log(2 ** 10); // 1024 (potencia) // ── Comparación ─────────────────────────────────────── console.log(5 > 3); // true console.log("a" < "b"); // true (lexicográfico) // ── Lógicos ────────────────────────────────────────── console.log(true && false); // false (AND) console.log(true || false); // true (OR) console.log(!true); // false (NOT) // ── ?? (Nullish Coalescing) — ES2020 ───────────────── // Devuelve el lado derecho solo si izquierda es null/undefined const nombre = null; console.log(nombre ?? "Consumidor final"); // "Consumidor final" console.log(0 ?? "default"); // 0 ← || daría "default", ?? no // ── ?. (Optional Chaining) — ES2020 ───────────────── // Accede a propiedades sin lanzar error si la base es null/undefined const cliente = { nombre: "Ana", direccion: null }; console.log(cliente?.direccion?.calle); // undefined (no error) console.log(cliente?.telefono?.trim()); // undefined (no error) // ── Asignación combinada ───────────────────────────── let x = 10; x += 5; // x = 15 x *= 2; // x = 30 x ??= 99; // x sigue 30 — ??= asigna solo si es null/undefined
Control de Flujo
if/else, switch, for, while y los operadores ternario. La base de toda lógica de negocio.
// ── if / else if / else ─────────────────────────────── function clasificarStock(cantidad) { if (cantidad <= 0) return 'agotado'; else if (cantidad <= 5) return 'crítico'; else if (cantidad <= 20) return 'bajo'; else return 'normal'; } // ── Operador ternario (condición ? verdadero : falso) ─ const stock = 3; const badge = stock > 0 ? 'disponible' : 'agotado'; // ── switch ──────────────────────────────────────────── function descripcionPago(tipo) { switch (tipo) { case 'efectivo': return 'Pago en efectivo'; case 'tarjeta': return 'Tarjeta bancaria'; case 'transferencia': return 'Transferencia SPEI'; default: return 'Forma de pago desconocida'; } } // ── for...of — iterar arrays (el más usado) ─────────── const productos = ['arrachera', 'costilla', 'chorizo']; for (const p of productos) { console.log(p); } // ── for...in — iterar propiedades de objeto ─────────── const precios = { arrachera: 189, costilla: 145, chorizo: 98 }; for (const clave in precios) { console.log(`${clave}: $${precios[clave]}`); } // ── while ───────────────────────────────────────────── let intentos = 0; while (intentos < 3) { console.log(`Intento ${intentos + 1}`); intentos++; }
Funciones y Arrow Functions
Las funciones son ciudadanos de primera clase en JavaScript — se pueden asignar a variables, pasar como argumentos y retornar desde otras funciones.
// ── Declaración de función (hoisted) ───────────────── function sumar(a, b) { return a + b; } // ── Expresión de función ────────────────────────────── const restar = function(a, b) { return a - b; }; // ── Arrow function ES6 (la más usada hoy) ───────────── const multiplicar = (a, b) => a * b; // return implícito const doble = n => n * 2; // paréntesis opcionales con 1 param const saludar = () => 'Hola!'; // sin parámetros // ── Parámetros por defecto ─────────────────────────── const formatearPrecio = (precio, moneda = 'MXN') => `$${precio.toFixed(2)} ${moneda}`; console.log(formatearPrecio(189.5)); // "$189.50 MXN" console.log(formatearPrecio(25.0, 'USD')); // "$25.00 USD" // ── Rest parameters (...args) ──────────────────────── const sumarTodo = (...numeros) => numeros.reduce((acc, n) => acc + n, 0); console.log(sumarTodo(10, 20, 30, 40)); // 100 // ── Closure: función que "recuerda" su contexto ─────── function crearContador(inicio = 0) { let cuenta = inicio; return { incrementar: () => ++cuenta, obtener: () => cuenta, reset: () => { cuenta = inicio; }, }; } const c = crearContador(10); console.log(c.incrementar()); // 11 console.log(c.incrementar()); // 12 console.log(c.obtener()); // 12
Arrays — map, filter, reduce
Los métodos funcionales de arrays son los más usados en JavaScript moderno. Dominándolos, el 80% de las transformaciones de datos se vuelven triviales.
const ventas = [ { id: 1, producto: 'Arrachera', cantidad: 2, precio: 189 }, { id: 2, producto: 'Costilla', cantidad: 3, precio: 145 }, { id: 3, producto: 'Chorizo', cantidad: 1, precio: 98 }, { id: 4, producto: 'Arrachera', cantidad: 5, precio: 189 }, ]; // ── map: transforma cada elemento ──────────────────── const totalesPorVenta = ventas.map(v => ({ id: v.id, total: v.cantidad * v.precio, })); // [{ id:1, total:378 }, { id:2, total:435 }, ...] // ── filter: filtra elementos ────────────────────────── const soloArrachera = ventas.filter(v => v.producto === 'Arrachera'); // [{ id:1, ... }, { id:4, ... }] // ── reduce: acumula a un solo valor ────────────────── const totalGeneral = ventas.reduce( (acc, v) => acc + (v.cantidad * v.precio), 0 ); // 378 + 435 + 98 + 945 = 1856 // ── find / findIndex ────────────────────────────────── const venta1 = ventas.find(v => v.id === 2); // { id:2, ... } const idx = ventas.findIndex(v => v.id === 3); // 2 // ── some / every ───────────────────────────────────── const hayCaros = ventas.some(v => v.precio > 180); // true const todosBarat = ventas.every(v => v.precio < 200); // true // ── flat / flatMap ──────────────────────────────────── const anidado = [[1,2], [3,4], [5]].flat(); // [1,2,3,4,5] // ── Spread operator ────────────────────────────────── const nuevaVenta = { id: 5, producto: 'Bistec', cantidad: 2, precio: 160 }; const todasLasVentas = [...ventas, nuevaVenta]; // no muta el original
Objetos y Spread
Los objetos son el tipo de dato más importante de JavaScript. Todo en el stack — ventas, productos, usuarios — viaja como objetos JSON.
// ── Crear objeto ────────────────────────────────────── const producto = { id: 'prod-001', nombre: 'Arrachera Premium', precio: 189.50, activo: true, tags: ['premium', 'res'], proveedor:{ nombre: 'Ganadería Norte', id: 'prov-12' }, }; // ── Acceso a propiedades ────────────────────────────── console.log(producto.nombre); // notación punto console.log(producto['precio']); // notación corchete console.log(producto.proveedor.nombre); // anidado console.log(producto.noExiste ?? 'N/A'); // undefined → 'N/A' // ── Spread: copiar/mezclar objetos ─────────────────── const actualizado = { ...producto, precio: 195, stock: 42 }; // producto original no se modifica // ── Métodos de Object ───────────────────────────────── console.log(Object.keys(producto)); // ['id','nombre','precio',...] console.log(Object.values(producto)); // ['prod-001','Arrachera',...] console.log(Object.entries(producto)); // [['id','prod-001'],['nombre',...]] // ── Shorthand properties (ES6) ──────────────────────── const nombre = 'Bistec'; const precio = 160; const nuevoProducto = { nombre, precio }; // { nombre: 'Bistec', precio: 160 } // ── Computed property names ────────────────────────── const campo = 'precio'; const update = { [campo]: 199 }; // { precio: 199 }
Desestructuración
Extraer valores de objetos y arrays en variables de forma concisa. Una de las features ES6 más usadas en React, Moleculer y Node.js.
// ── Desestructuración de objetos ────────────────────── const venta = { id: 'v-001', total: 450, sucursal: 'suc-07', iva: 72 }; const { id, total, sucursal } = venta; console.log(id, total); // 'v-001' 450 // Renombrar al extraer const { id: ventaId, total: monto } = venta; console.log(ventaId, monto); // 'v-001' 450 // Con valores por defecto const { descuento = 0, notas = 'Sin notas' } = venta; console.log(descuento); // 0 (no existía en venta) // ── Desestructuración de arrays ─────────────────────── const [primero, segundo, ...resto] = [10, 20, 30, 40, 50]; console.log(primero); // 10 console.log(resto); // [30, 40, 50] // ── En parámetros de función (muy común en React/Moleculer) function procesarVenta({ id, total, sucursal, iva = 0 }) { console.log(`Venta ${id} en ${sucursal} — Total: $${total + iva}`); } procesarVenta(venta); // ── Intercambio de variables ───────────────────────── let a = 1, b = 2; [a, b] = [b, a]; console.log(a, b); // 2 1
Promesas — Promise
JavaScript es single-threaded pero asíncrono. Las Promesas representan un valor que estará disponible en el futuro — el resultado de una consulta a BD, una llamada a API, leer un archivo.
// Una Promise tiene 3 estados: pending → fulfilled | rejected // ── Crear una Promise ───────────────────────────────── const obtenerProducto = (id) => new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, nombre: 'Arrachera', precio: 189 }); // éxito } else { reject(new Error('ID inválido')); // error } }, 100); }); // ── Consumir con .then() / .catch() ─────────────────── obtenerProducto(1) .then(producto => { console.log('Producto:', producto.nombre); return producto.precio * 1.16; // precio con IVA }) .then(precioConIva => { console.log('Con IVA:', precioConIva); }) .catch(err => { console.error('Error:', err.message); }) .finally(() => { console.log('Siempre se ejecuta'); // loading=false, cleanup }); // ── Promise.all: esperar varias a la vez ────────────── const [prod, proveedor] = await Promise.all([ obtenerProducto(1), obtenerProveedor(5), ]); // Espera ambas → latencia = MAX(t1,t2) en lugar de t1+t2 // ── Promise.allSettled: aunque alguna falle ─────────── const resultados = await Promise.allSettled([p1, p2, p3]); resultados.forEach(r => { if (r.status === 'fulfilled') console.log(r.value); else console.error(r.reason); });
Async / Await
Sintaxis que hace que el código asíncrono se lea como si fuera síncrono. Es azúcar sintáctica sobre Promesas — por debajo todo sigue siendo Promises.
// ── async/await: promesas con sintaxis clara ────────── async function registrarVenta(datos) { try { // await "pausa" la función hasta que la promesa resuelve const stockOk = await verificarStock(datos.items); if (!stockOk) throw new Error('Stock insuficiente'); const venta = await guardarEnDB(datos); const cfdi = await timbrarCFDI(venta); return { venta, cfdi }; } catch (error) { console.error('Error en venta:', error.message); throw error; // re-lanzar para el caller } } // ── Arrow async function ────────────────────────────── const fetchProducto = async (id) => { const res = await fetch(`/api/productos/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }; // ── Paralelo con async/await ────────────────────────── async function cargarDashboard(sucursalId) { // Mal: secuencial (t1 + t2 + t3) // const ventas = await getVentas(sucursalId); // const stock = await getStock(sucursalId); // const mermas = await getMermas(sucursalId); // Bien: paralelo (MAX(t1, t2, t3)) const [ventas, stock, mermas] = await Promise.all([ getVentas(sucursalId), getStock(sucursalId), getMermas(sucursalId), ]); return { ventas, stock, mermas }; } // ── for await...of — iterar streams asíncronos ──────── async function procesarLote(ids) { for await (const producto of streamProductos(ids)) { await procesarProducto(producto); } }
await en el nivel raíz de un archivo CommonJS (require). En módulos ESM (import/export) sí puedes usar Top-Level Await. En Node.js 20 con "type": "module" en package.json, puedes escribir await fetch(...) directamente sin envolver en una función async.
Módulos ESM
Los módulos permiten dividir el código en archivos reutilizables. ES Modules (ESM) con import/export es el estándar moderno, soportado en browsers y Node 12+.
// ── utils/precios.js — EXPORTAR ────────────────────── // Named exports (puedes tener varios) export const IVA = 0.16; export function conIva(precio) { return precio * (1 + IVA); } export const formatMXN = (n) => new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(n); // Default export (solo uno por archivo) export default class CalculadoraPrecios { calcularTotal(items) { return items.reduce((s, i) => s + conIva(i.precio * i.cantidad), 0); } } // ── main.js — IMPORTAR ─────────────────────────────── import CalculadoraPrecios, { IVA, conIva, formatMXN } from './utils/precios.js'; console.log(conIva(100)); // 116 console.log(formatMXN(1856.50)); // $1,856.50 // Import todo como namespace import * as PreciosUtils from './utils/precios.js'; console.log(PreciosUtils.IVA); // 0.16 // Import dinámico (lazy loading) const { generarReporte } = await import('./reportes.js'); // CommonJS (Node.js legacy — evitar en proyectos nuevos) // const { conIva } = require('./utils/precios'); ← CJS
Clases y Herencia
Las clases ES6 son azúcar sintáctica sobre el sistema de prototipos de JavaScript. Útiles para modelar entidades de negocio con estado y comportamiento encapsulados.
// ── Clase base ──────────────────────────────────────── class Entidad { #id; // campo privado (ES2022) constructor(id) { this.#id = id ?? crypto.randomUUID(); this.creadoEn = new Date().toISOString(); } get id() { return this.#id; } toJSON() { return { id: this.#id, creadoEn: this.creadoEn }; } } // ── Clase hija con extends ──────────────────────────── class Producto extends Entidad { constructor({ id, nombre, precio, unidad = 'kg' }) { super(id); // llama al constructor padre this.nombre = nombre; this.precio = precio; this.unidad = unidad; this.#stock = 0; } #stock; // privado get stock() { return this.#stock; } set stock(val) { if (val < 0) throw new Error('Stock negativo'); this.#stock = val; } precioConIva() { return this.precio * 1.16; } toJSON() { return { ...super.toJSON(), nombre: this.nombre, precio: this.precio }; } // Método estático (no requiere instancia) static desdeBD(row) { return new Producto({ id: row.id, nombre: row.nombre, precio: row.precio_unitario }); } } // ── Uso ─────────────────────────────────────────────── const arr = new Producto({ nombre: 'Arrachera', precio: 189 }); arr.stock = 42; console.log(arr.precioConIva()); // 219.24 console.log(arr instanceof Producto); // true console.log(arr instanceof Entidad); // true (herencia)
JavaScript en el ERP Carnicerías
Todo lo aprendido en acción: el módulo de cálculo de ventas del POS usando JS moderno — desestructuración, array methods, async/await y módulos.
// ── Constantes del negocio ──────────────────────────── export const IVA_TASA = 0.16; export const IEPS_CARNE = 0.0; // exento export const STOCK_MINIMO = 5; // ── Formateo de moneda mexicana ─────────────────────── export const formatMXN = (n) => new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', minimumFractionDigits: 2, }).format(n); // ── Calcular línea de venta ─────────────────────────── export function calcularLinea({ precio, cantidad, descuento = 0 }) { const subtotal = precio * cantidad; const descuentoN = subtotal * descuento; const base = subtotal - descuentoN; const iva = base * IVA_TASA; return { subtotal, descuentoN, base, iva, total: base + iva }; } // ── Calcular ticket completo ────────────────────────── export function calcularTicket(items) { const lineas = items.map(item => ({ ...item, ...calcularLinea(item), })); const totales = lineas.reduce( (acc, l) => ({ subtotal: acc.subtotal + l.subtotal, descuentos: acc.descuentos + l.descuentoN, iva: acc.iva + l.iva, total: acc.total + l.total, }), { subtotal: 0, descuentos: 0, iva: 0, total: 0 } ); return { lineas, ...totales }; } // ── Verificar stock antes de vender ────────────────── export async function verificarDisponibilidad(items, sucursalId) { const checks = await Promise.all( items.map(async ({ productoId, cantidad }) => { const { stock } = await getStock(productoId, sucursalId); return { productoId, disponible: stock >= cantidad, stock, cantidad }; }) ); const sinStock = checks.filter(c => !c.disponible); if (sinStock.length > 0) { const lista = sinStock.map(c => `${c.productoId} (pide ${c.cantidad}, hay ${c.stock})`).join(', '); throw new Error(`Sin stock: ${lista}`); } return true; }