ES2015–2022
JavaScript
v1.0.0
⚡ CultivaMetrics — Guía de Modernización JS

JavaScript ES6+ Patterns
Referencia para Devs

Guía práctica para migrar el codebase de CultivaMetrics de callbacks y ES5 a patrones modernos. Cada sección muestra el código legacy y su equivalente moderno con ejemplos del dominio de analítica de marketing.

8+
Categorías
25+
Patrones
15
Best Practices
ES2022
Versión target
📦
Variables // const · let · var
Declaración correcta de variables
ES6
Legacy
var campanas = 0;
var clienteActivo = true;
var nombre = "CultivaMetrics";

// Problema: var tiene scope
// de función, no de bloque
for (var i = 0; i < 3; i++) {
  // i "escapa" al scope
}
console.log(i); // 3 (leak!)
Moderno
const campanias = 0;    // inmutable
const clienteActivo = true;
const nombre = "CultivaMetrics";

// let solo cuando reasignas
let totalClicks = 0;

for (let i = 0; i < 3; i++) {
  // i confinada al bloque
}
// i no existe aquí ✓
Template literals
ES6
Legacy
const cliente = "Acme Corp";
const ctr = 3.7;

const msg = "Cliente: " + cliente +
  " | CTR: " + ctr + "%";

// Multi-línea: imposible limpio
const html = "<div>" +
  "<h2>" + cliente + "</h2>" +
  "</div>";
Moderno
const cliente = "Acme Corp";
const ctr = 3.7;

const msg = `Cliente: ${cliente} | CTR: ${ctr}%`;

// Multi-línea y expresiones
const html = `
  <div>
    <h2>${cliente}</h2>
    <p>CTR: ${(ctr * 1.1).toFixed(2)}%</p>
  </div>
`;
Arrow functions — léxico de this
ES6
Legacy — problema con this
var CampaniaTracker = function() {
  this.clicks = 0;
};

CampaniaTracker.prototype.iniciar = function() {
  setInterval(function() {
    this.clicks++;  // ❌ this = window/undefined
    console.log(this.clicks);
  }, 1000);
};
Moderno — arrow preserva this
class CampaniaTracker {
  clicks = 0;

  // Arrow field = this siempre correcto
  registrarClick = () => {
    this.clicks++;
  };

  iniciar() {
    setInterval(() => {
      this.clicks++;  // ✓ this = instancia
      console.log(this.clicks);
    }, 1000);
  }
}
🔓
Desestructuración // objects · arrays · params
Desestructuración de objetos
ES6
Legacy
const campania = {
  id: "cm-2024-01",
  nombre: "Black Friday",
  presupuesto: 5000,
  metricas: { ctr: 4.2, roas: 8.1 }
};

var id = campania.id;
var nombre = campania.nombre;
var ctr = campania.metricas.ctr;
var roas = campania.metricas.roas;
Moderno
const campania = {
  id: "cm-2024-01",
  nombre: "Black Friday",
  presupuesto: 5000,
  metricas: { ctr: 4.2, roas: 8.1 }
};

const { id, nombre, metricas: { ctr, roas } } = campania;

// Alias + default
const { presupuesto: budget = 1000 } = campania;
Parámetros de función
ES6
Legacy
function calcularROAS(campania) {
  var ingresos = campania.ingresos;
  var gasto = campania.gasto;
  var nombre = campania.nombre;
  return {
    nombre: nombre,
    roas: ingresos / gasto
  };
}
Moderno
const calcularROAS = ({
  nombre,
  ingresos,
  gasto = 1   // default evita /0
}) => ({
  nombre,
  roas: ingresos / gasto
});

// Shorthand: nombre: nombre → nombre
Desestructuración de arrays
ES6
Legacy
var top3 = ["Google Ads", "Meta", "TikTok"];

var primero = top3[0];
var segundo = top3[1];
var resto = top3.slice(2);

// Intercambio verboso
var tmp = primero;
primero = segundo;
segundo = tmp;
Moderno
const [primero, segundo, ...resto] = [
  "Google Ads", "Meta", "TikTok", "LinkedIn"
];

// Intercambio sin tmp variable
[primero, segundo] = [segundo, primero];

// Skip elementos
const [,, tercero] = top3;
Retorno múltiple de funciones
// Función que retorna múltiples métricas
const analizarCampania = (datos) => {
  const ctr = datos.clicks / datos.impresiones * 100;
  const roas = datos.ingresos / datos.gasto;
  const cpa = datos.gasto / datos.conversiones;
  return { ctr, roas, cpa };  // shorthand
};

// Consumir con desestructuración directa
const { ctr, roas } = analizarCampania(datosCampania);
console.log(`CTR: ${ctr.toFixed(2)}% | ROAS: ${roas.toFixed(1)}x`);
🌊
Spread / Rest // immutabilidad · composición
Actualización inmutable de estado
ES6
Legacy — mutación directa
var campania = {
  nombre: "Summer Sale",
  activa: false,
  metricas: { clicks: 1200 }
};

// ❌ Mutación directa — bugs en React/stores
campania.activa = true;
campania.metricas.clicks = campania.metricas.clicks + 50;

// Arrays: métodos que mutan
var canales = ["Google", "Meta"];
canales.push("TikTok");  // muta el original
Moderno — spread immutable
const campania = {
  nombre: "Summer Sale",
  activa: false,
  metricas: { clicks: 1200 }
};

// ✓ Nueva copia, original intacto
const campaniaActiva = {
  ...campania,
  activa: true,
  metricas: { ...campania.metricas, clicks: 1250 }
};

// Arrays sin mutación
const canales = ["Google", "Meta"];
const canalesActualizados = [...canales, "TikTok"];
Merge de configuraciones
const defaultConfig = {
  timeout: 5000,
  retries: 3,
  formato: "json",
  metricas: ["ctr", "roas"]
};

const userConfig = {
  timeout: 10000,
  metricas: ["ctr", "cpa", "roas"]
};

// userConfig gana (último spread)
const config = { ...defaultConfig, ...userConfig };
// { timeout: 10000, retries: 3, formato: "json", metricas: [...] }
Rest en parámetros
// Acepta N canales sin array explícito
const combinarMetricas = (nombre, ...canales) => ({
  nombre,
  totalCanales: canales.length,
  clicksTotal: canales.reduce(
    (sum, c) => sum + c.clicks, 0
  )
});

combinarMetricas(
  "Q4 2024",
  { canal: "Google", clicks: 4500 },
  { canal: "Meta",   clicks: 2800 },
  { canal: "TikTok", clicks: 1900 }
);
Async / Await // promesas · error handling
Callbacks → Async/Await
ES2017
Legacy — callback hell
fetchCliente(123, function(err, cliente) {
  if (err) {
    console.error("Error cliente:", err);
    return;
  }
  fetchCampanias(cliente.id, function(err, camps) {
    if (err) {
      console.error("Error campanias:", err);
      return;
    }
    calcularROAS(camps, function(err, roas) {
      // Callback hell continúa...
    });
  });
});
Moderno — async/await lineal
const getDashboardData = async (clienteId) => {
  try {
    const cliente = await fetchCliente(clienteId);

    // Paralelo: no hay dependencia entre sí
    const [campanias, presupuesto] = await Promise.all([
      fetchCampanias(cliente.id),
      fetchPresupuesto(cliente.id)
    ]);

    return { cliente, campanias, presupuesto };
  } catch (err) {
    console.error("Dashboard error:", err);
    throw err;
  }
};
Retry con backoff exponencial
perf
const fetchConRetry = async (url, reintentos = 3) => {
  for (let i = 0; i < reintentos; i++) {
    try {
      return await fetch(url);
    } catch (err) {
      if (i === reintentos - 1) throw err;
      // Espera: 1s, 2s, 4s…
      await new Promise(
        res => setTimeout(res, 1000 * 2 ** i)
      );
    }
  }
};
Promise combinators
const fuentes = [
  fetchGoogleAds(clienteId),
  fetchMetaAds(clienteId),
  fetchTikTokAds(clienteId)
];

// all → falla si alguna falla
const [google, meta, tiktok] =
  await Promise.all(fuentes);

// allSettled → da resultado de cada una
const resultados = await Promise.allSettled(fuentes);
const exitosas = resultados
  .filter(r => r.status === "fulfilled")
  .map(r => r.value);
🔄
Programación Funcional // map · filter · reduce
Pipeline de transformación de datos de campañas
ES6perf
Legacy — loops imperativos
var campanias = [
  { nombre: "Google Ads", gasto: 2000, ingresos: 8500, activa: true },
  { nombre: "Meta Ads",   gasto: 1500, ingresos: 3200, activa: true },
  { nombre: "TikTok",     gasto: 800,  ingresos: 500,  activa: false }
];

var activasConROAS = [];
for (var i = 0; i < campanias.length; i++) {
  if (campanias[i].activa) {
    var c = campanias[i];
    activasConROAS.push({
      nombre: c.nombre,
      roas: c.ingresos / c.gasto
    });
  }
}
activasConROAS.sort(function(a,b){ return b.roas - a.roas; });
Moderno — pipeline declarativo
const campanias = [
  { nombre: "Google Ads", gasto: 2000, ingresos: 8500, activa: true },
  { nombre: "Meta Ads",   gasto: 1500, ingresos: 3200, activa: true },
  { nombre: "TikTok",     gasto: 800,  ingresos: 500,  activa: false }
];

const activasConROAS = campanias
  .filter(({ activa }) => activa)
  .map(({ nombre, gasto, ingresos }) => ({
    nombre,
    roas: (ingresos / gasto).toFixed(2)
  }))
  .sort((a, b) => b.roas - a.roas);
Reduce — aggregación de métricas
const resumen = campanias.reduce((acc, c) => ({
  ...acc,
  totalGasto:    acc.totalGasto + c.gasto,
  totalIngresos: acc.totalIngresos + c.ingresos,
  numCampanias:  acc.numCampanias + 1
}), {
  totalGasto: 0,
  totalIngresos: 0,
  numCampanias: 0
});

// ROAS global calculado al final
const roasGlobal = resumen.totalIngresos / resumen.totalGasto;
Composición de funciones
// Funciones puras, composables
const soloActivas = cs => cs.filter(c => c.activa);
const conROAS = cs => cs.map(c => ({
  ...c, roas: c.ingresos / c.gasto
}));
const ordenarPorROAS = cs =>
  [...cs].sort((a,b) => b.roas - a.roas);

// pipe: aplica fns de izq a der
const pipe = (...fns) => x =>
  fns.reduce((v, f) => f(v), x);

const procesarCampanias = pipe(
  soloActivas, conROAS, ordenarPorROAS
);
🏗️
Clases Modernas // private fields · ES2022
Clase con campos privados y métodos estáticos
ES2022
class Campania {
  // Campos privados con # (ES2022)
  #gasto = 0;
  #ingresos = 0;
  #historial = [];

  // Campo estático compartido
  static roasMinimo = 2.0;

  constructor({ nombre, gasto, ingresos }) {
    this.nombre = nombre;
    this.#gasto = gasto;
    this.#ingresos = ingresos;
  }

  // Getter computado
  get roas() {
    return this.#ingresos / this.#gasto;
  }

  get rentable() {
    return this.roas >= Campania.roasMinimo;
  }

  // Método público que modifica privado
  actualizarGasto(nuevo) {
    this.#historial.push({ gasto: this.#gasto, ts: new Date() });
    this.#gasto = nuevo;
  }

  // Factory method estático
  static fromAPI(data) {
    return new Campania({
      nombre: data.name,
      gasto: data.spend,
      ingresos: data.revenue
    });
  }
}

const c = new Campania({ nombre: "Black Friday", gasto: 5000, ingresos: 22000 });
console.log(c.roas);       // 4.4
console.log(c.rentable);   // true
// console.log(c.#gasto) // ❌ SyntaxError — privado
🔧
Operadores Modernos // ?. · ?? · ??=
Optional chaining
ES2020
Legacy
var ciudad =
  cliente &&
  cliente.direccion &&
  cliente.direccion.ciudad;

var ctr =
  data &&
  data.metricas &&
  data.metricas.ctr;
Moderno
const ciudad =
  cliente?.direccion?.ciudad;

const ctr = data?.metricas?.ctr;

// También con llamadas a método
const total = campania.calcular?.();

// Arrays
const primero = arr?.[0];
Nullish coalescing + assignment
ES2020ES2021
Legacy
// || falla con 0 y ""
var ctr = data.ctr || 0;
// Si ctr es 0: devuelve 0... pero
// si es string vacío: devuelve 0

var n = config.nombre || "Sin nombre";
// Si nombre es "" devuelve "Sin nombre"
// aunque fue asignado explícitamente
Moderno
// ?? solo hace fallback si null/undefined
const ctr = data.ctr ?? 0;
// 0 se conserva como 0 ✓

const n = config.nombre ?? "Sin nombre";
// "" se conserva como "" ✓

// Logical assignment
config.timeout ??= 5000;  // solo si null/undef
obj.cache ||= {};          // si falsy
obj.hits &&= obj.hits + 1; // si truthy
📦
Módulos ES6 // import · export · dynamic
Estructura de módulos CultivaMetrics
ES6
analytics/campanias.js — exports
// Named exports — se pueden importar selectivamente
export const calcularROAS = ({ ingresos, gasto }) =>
  ingresos / gasto;

export const calcularCPA = ({ gasto, conversiones }) =>
  gasto / conversiones;

export const ROAS_MINIMO = 2.0;

// Default export — la función principal del módulo
export default class CampaniaAnalytics {
  constructor(config) { this.config = config; }
  analizar(data) { /* ... */ }
}
dashboard.js — imports
// Default + named en un import
import CampaniaAnalytics, {
  calcularROAS,
  calcularCPA,
  ROAS_MINIMO
} from "./analytics/campanias.js";

// Namespace import
import * as Analytics from "./analytics/campanias.js";

// Dynamic import (code splitting)
const cargarGraficas = async () => {
  const { ChartBuilder } = await import("./charts/index.js");
  return new ChartBuilder();
};
15 Buenas Prácticas ES6+ — CultivaMetrics
1
const por defectoSolo usa let cuando necesites reasignar. Nunca var.
2
Arrow functions para callbacksPreserve el contexto de this sin .bind().
3
Template literalsNo más concatenación con +. Usa backticks y ${expr}.
4
Desestructura siempreParámetros, retornos de API, props — reduce repetición.
5
async/await sobre .then()Código más legible; usa try/catch para errores.
6
No mutes datosSpread operator y métodos que retornan nuevos arrays.
7
Optional chaining (?.) Elimina "Cannot read property of undefined".
8
Nullish coalescing (??)Usa ?? en vez de || para defaults cuando 0/"" son válidos.
9
Array methods funcionalesmap/filter/reduce sobre for loops — más expresivos.
10
Módulos ES6Una responsabilidad por módulo. Named exports rastreables.
11
Funciones purasSin side effects — más fáciles de testear y componer.
12
Nombres descriptivoscalcularROAS, no calc. Código autodocumentado.
13
Funciones pequeñasSingle Responsibility Principle — una tarea por función.
14
Errores con try/catchSiempre en funciones async. No silencies errores.
15
Campos privados (#)Usa # para encapsular estado interno en clases (ES2022).