Capítulo 01

¿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.

🌐
Multiparadigma
Imperativo, orientado a objetos y funcional. Tú eliges el estilo según el problema.
Interpretado / JIT
No se compila a binario. El motor V8 (Chrome/Node) lo compila en tiempo real — muy rápido.
🔄
Single-threaded
Un solo hilo de ejecución, pero con un Event Loop que maneja miles de operaciones I/O concurrentes.
🦆
Tipado dinámico
Las variables no tienen tipo fijo — puede cambiar en runtime. TypeScript añade tipos estáticos opcionales.
primer-programa.js JS
// 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"
Consola interactiva — ejecuta JS real ▶ LIVE
const nombre = "VLIM"; const año = 2024; console.log(`Hola desde ${nombre}`); console.log(`Año: ${año}`); console.log(2.5 * 189);
// output Haz clic en Ejecutar…

Capítulo 02

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.

TipoEjemplotypeofNotas
string"hola" `texto`"string"Texto. Las template literals con ` permiten interpolación.
number42 3.14 NaN Infinity"number"IEEE 754 de 64 bits. NaN es "Not a Number" pero typeof NaN === "number" 😅
bigint9007199254740993n"bigint"Enteros arbitrariamente grandes. Usar para IDs de BD muy grandes.
booleantrue false"boolean"Resultado de comparaciones y condiciones.
undefinedundefined"undefined"Variable declarada pero sin valor asignado.
nullnull"object" ⚠️Ausencia intencional de valor. Bug histórico: typeof null === "object".
symbolSymbol("id")"symbol"Identificadores únicos. Usado para propiedades privadas.
object{} [] new Date()"object"Colección de propiedades clave-valor. Arrays y funciones son objetos.
tipos.js JS
// 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)
Usa siempre === (triple igual) El operador == hace coerción de tipos antes de comparar, lo que produce resultados inesperados: 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.

Capítulo 03

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.

variables.js — const, let, var ES6
// ── 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
Diagrama de Scope — dónde vive cada variable
🌍 Global scope
const MAX = 100 var viejo = "x"
🔧 Function scope — function calcular()
let subtotal = 0 const IVA = 0.16 var temp = true ← visible en toda la función
🧱 Block scope — if / for / {}
let item = ... ← solo aquí const desc = 0.1 ← solo aquí

Capítulo 04

Operadores Esenciales

Aritméticos, de comparación, lógicos y los modernos operadores de coalescencia y encadenamiento opcional.

operadores.js ES2020
// ── 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
Prueba operadores en vivo ▶ LIVE
const precio = null; const final = precio ?? 100; console.log(final); const venta = { total: 500 }; console.log(venta?.cliente?.nombre ?? "Sin cliente"); console.log(2 ** 8); console.log(17 % 5);
// output Haz clic en Ejecutar…

Capítulo 05

Control de Flujo

if/else, switch, for, while y los operadores ternario. La base de toda lógica de negocio.

control-flujo.js JS
// ── 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++;
}

Capítulo 06

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.

function arrow => parámetros default rest ...args closures HOF
funciones.js — todas las formas ES6
// ── 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

Capítulo 07

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.

arrays.js — métodos esenciales ES6
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
Prueba map / filter / reduce ▶ LIVE
const nums = [10, 25, 3, 48, 17, 6, 42]; const grandes = nums.filter(n => n > 20); console.log(grandes); const dobles = nums.map(n => n * 2); console.log(dobles); const suma = nums.reduce((a, b) => a + b, 0); console.log("Suma:", suma); const max = Math.max(...nums); console.log("Max:", max);
// output Haz clic en Ejecutar…

Capítulo 08

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.

objetos.js ES6
// ── 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 }

Capítulo 09

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.

destructuring.js ES6
// ── 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

Capítulo 10

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.

promesas.js — estados y encadenamiento ASYNC
// 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);
});

Capítulo 11

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.js — el patrón moderno ASYNC
// ── 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 solo funciona dentro de async No puedes usar 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.

Capítulo 12

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 → main.js — export e import ESM
// ── 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

Capítulo 13

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.

clases.js — entidades del ERP ES6
// ── 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)

Capítulo 14

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.

pos/calculos.js — lógica real del ERP REAL
// ── 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;
}
Demo — Calculadora de ticket POS ▶ LIVE
const IVA = 0.16; function calcularLinea({ precio, cantidad, descuento = 0 }) { const subtotal = precio * cantidad; const desc = subtotal * descuento; const base = subtotal - desc; return { subtotal, base, iva: base * IVA, total: base * (1 + IVA) }; } const items = [ { nombre: "Arrachera", precio: 189, cantidad: 2 }, { nombre: "Costilla", precio: 145, cantidad: 3 }, { nombre: "Chorizo", precio: 98, cantidad: 1, descuento: 0.1 }, ]; const lineas = items.map(i => ({ ...i, ...calcularLinea(i) })); const total = lineas.reduce((s, l) => s + l.total, 0); lineas.forEach(l => console.log(`${l.nombre}: $${l.total.toFixed(2)}`)); console.log(`─────────────────`); console.log(`TOTAL: $${total.toFixed(2)}`);
// ticket POS Haz clic en Ejecutar ticket…
🥩
De aquí al stack completo Con JavaScript dominado, el siguiente paso es TypeScript — añade tipos estáticos a todo lo que aprendiste aquí. Después Node.js para el servidor, React para la interfaz, y finalmente Moleculer + BullMQ para la arquitectura distribuida del ERP. Cada curso está disponible en la biblioteca de VLIM.