agentsclimarketplace

Javascript

Skill 14BryanEspinoza/agent-stack/skills/javascript

Skills modulares para asistentes de codificación por IA. Define reglas, flujos de trabajo y estándares técnicos mediante archivos Markdowm.

Install
npx -y skills add 14BryanEspinoza/agent-stack --skill javascript

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 21 days oldThe repository was created 21 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Reglas de JavaScript vanilla ES6+ - ES2025, programación defensiva, módulos, DOM, fetch, APIs modernas, estructuras de datos, patrones avanzados

SKILL.md

26.3 KB, as published. Nobody here has run it

JavaScript Vanilla ES6+ - Reglas

JavaScript vanilla ES6+ es el enfoque preferido.

1. Instalación y Versión

Versión mínima

TecnologíaVersión Mínima
JavaScriptES2025+ (moderno)
Node.js22+

2. Cuándo USAR JavaScript

CasoJustificación
Interactividad dinámicaModales, acordeones, tabs
Manipulación del DOMCrear/actualizar contenido dinámicamente
Fetch/AJAXObtener/enviar datos sin recargar
Validación clienteValidación en tiempo real
APIs del navegadorLocalStorage, Geolocation, etc.

3. Cuándo NO USAR JavaScript

En lugar de...Usar...
Animaciones simplesCSS Transitions/Animations
Tooltips simplesCSS-only o title attribute
Modales simplesHTML <dialog>
Acordeones simplesHTML <details>/<summary>
Carousels simplesCSS scroll snap
Dropdowns simplesHTML <select>

4. APIs Preferidas

En lugar de...Usar...
varconst (por defecto), let (si reasigna)
functionArrow functions cuando corresponda
Callbacksasync/await + Promise
$.ajaxfetch
for loopsforEach, map, filter, reduce
Strings concatTemplate literals `hello ${name}`
setTimeout para animacionesCSS transitions
Animaciones con JSWeb Animations API
Detectar elementos en vistaIntersectionObserver

5. Estructuras de Datos Modernas

Map (claves de cualquier tipo)

const users = new Map();

users.set(42, { name: "Ana" });
users.set("admin", { name: "Admin" });
users.get(42); // { name: "Ana" }
users.has("admin"); // true
users.size; // 2
users.delete(42);
users.clear();

// Iteración
for (const [key, value] of users) {
  console.log(key, value);
}

WeakMap (claves objeto, sin prevenir GC)

let element = document.querySelector(".card");
const metadata = new WeakMap();

metadata.set(element, { timestamp: Date.now() });
// Cuando element se elimina del DOM y se recolecta,
// la entrada en WeakMap también se limpia automáticamente

metadata.get(element);

Set (valores únicos)

const tags = new Set(["js", "css", "html", "js"]);
// Set(3) { "js", "css", "html" }

tags.add("react");
tags.has("css"); // true
tags.size; // 4
tags.delete("css");

// Convertir array a valores únicos
const unique = [...new Set([1, 2, 2, 3, 3, 3])];
// [1, 2, 3]

Set Operations (ES2025)

const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

setA.union(setB);
// Set(6) { 1, 2, 3, 4, 5, 6 }

setA.intersection(setB);
// Set(2) { 3, 4 }

setA.difference(setB);
// Set(2) { 1, 2 }

setA.symmetricDifference(setB);
// Set(4) { 1, 2, 5, 6 }

setA.isSubsetOf(setB); // false
setA.isSupersetOf(setB); // false
setA.isDisjointFrom(setB); // false

WeakRef + FinalizationRegistry

// WeakRef: referencia débil que no impide GC
const ref = new WeakRef({ data: "pesado" });
const obj = ref.deref(); // undefined si ya fue recolectado

// FinalizationRegistry: callback cuando un objeto es recolectado
const registry = new FinalizationRegistry((heldValue) => {
  console.log("Recolectado:", heldValue);
});

let tracked = { id: 1 };
registry.register(tracked, "cache-item-1", tracked);
registry.unregister(tracked); // Cancelar registro

6. Nuevas APIs del Lenguaje (ES2024+)

Promise.withResolvers()

Reemplaza el patrón verbose de promesas externas:

// Antes
const promise = new Promise((resolve, reject) => {
  externalCallback((err, data) => {
    if (err) reject(err);
    else resolve(data);
  });
});

// Ahora
const { promise, resolve, reject } = Promise.withResolvers();
externalCallback((err, data) => {
  if (err) reject(err);
  else resolve(data);
});

Object.groupBy() / Map.groupBy()

const products = [
  { name: "Laptop", category: "tech" },
  { name: "Shirt", category: "clothing" },
  { name: "Phone", category: "tech" },
];

// Object.groupBy (cuando las claves son strings)
const byCategory = Object.groupBy(products, (p) => p.category);
// { tech: [Laptop, Phone], clothing: [Shirt] }

// Map.groupBy (cuando las claves son objetos)
const byPriceRange = Map.groupBy(products, (p) => {
  if (p.price < 50) return "budget";
  if (p.price < 500) return "mid";
  return "premium";
});

structuredClone() — Deep cloning nativo

const original = { user: { name: "Ana", tags: ["js"] } };
const cloned = structuredClone(original);

cloned.user.name = "Luis"; // No afecta al original
cloned.user.tags.push("ts"); // No afecta al original

// ✅ Seguro con fechas, Map, Set, RegExp, ArrayBuffer
const complex = {
  date: new Date(),
  map: new Map([["key", "value"]]),
  set: new Set([1, 2, 3]),
};
structuredClone(complex); // ✅ Funciona

// ❌ No clona funciones, Symbol, WeakMap, DOM nodes

Array.fromAsync()

async function* asyncGenerator() {
  yield await Promise.resolve(1);
  yield await Promise.resolve(2);
  yield await Promise.resolve(3);
}

const arr = await Array.fromAsync(asyncGenerator());
// [1, 2, 3] — secuencial, respeta orden

// Equivalente a:
const results = [];
for await (const item of asyncGenerator()) {
  results.push(item);
}

Error.cause

try {
  await fetch("/api/data");
} catch (error) {
  // Preservar el error original con contexto adicional
  throw new Error("Failed to load data", { cause: error });
}

// Al capturar:
// error.message      → "Failed to load data"
// error.cause.message → error original de fetch

JSON.parse con context.source

const json = '{"count": 42, "name": "test"}';

JSON.parse(json, (key, value, context) => {
  console.log(`Key: ${key}, Source: ${context.source}`);
  // "count", "42"
  // "name", "\"test\""
  return value;
});

7. Patrones Avanzados

Proxy + Reflect (metaprogramación)

// Validación de propiedades con Proxy
const validator = {
  set(target, key, value) {
    if (key === "age" && (typeof value !== "number" || value < 0)) {
      throw new Error("Age must be a positive number");
    }
    return Reflect.set(target, key, value);
  },
};

const user = new Proxy({}, validator);
user.age = 25; // ✅
user.age = -5; // ❌ Error

// Logging automático con Proxy
const logger = {
  get(target, property, receiver) {
    console.log(`GET ${property}`);
    return Reflect.get(target, property, receiver);
  },
};

const tracked = new Proxy({ name: "Ana" }, logger);
tracked.name; // console: "GET name"

Intl.Segmenter (segmentación de texto)

// Segmentar por palabras
const wordSegmenter = new Intl.Segmenter("es", { granularity: "word" });
const words = [...wordSegmenter.segment("Hola mundo, ¿cómo estás?")];
// [{ segment: "Hola" }, { segment: " " }, { segment: "mundo" }, ...]

// Segmentar por oraciones
const sentenceSegmenter = new Intl.Segmenter("es", { granularity: "sentence" });
const text = "Primera oración. Segunda oración. ¿Tercera?";
const sentences = [...sentenceSegmenter.segment(text)];
// [{ segment: "Primera oración. " }, { segment: "Segunda oración. " }, ...]

// Segmentar por grafemas (caracteres visibles)
const graphemeSegmenter = new Intl.Segmenter("es", { granularity: "grapheme" });
const chars = [...graphemeSegmenter.segment("café")].map((s) => s.segment);
// ["c", "a", "f", "é"]

Iterator Helpers (ES2025)

function* naturals() {
  let i = 1;
  while (true) yield i++;
}

// take() — tomar N elementos
const first5 = naturals().take(5).toArray();
// [1, 2, 3, 4, 5]

// map() / filter() sobre iteradores (sin crear arrays intermedios)
const evens = naturals()
  .filter((n) => n % 2 === 0)
  .take(5)
  .toArray();
// [2, 4, 6, 8, 10]

// reduce()
const sum = naturals()
  .take(100)
  .reduce((a, b) => a + b, 0);
// 5050

// zip() — combinar iteradores
const names = ["Ana", "Luis", "Carlos"];
const ages = [25, 30, 28];
const paired = Iterator.zip(names, ages).toArray();
// [["Ana", 25], ["Luis", 30], ["Carlos", 28]]

// flatMap()
const words = ["hello", "world"];
const letters = words
  .values()
  .flatMap((w) => w.split(""))
  .toArray();
// ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]

// drop()
const withoutFirst = naturals().drop(10).take(3).toArray();
// [11, 12, 13]

8. Selección DOM

// Selección simple
const element = document.querySelector(".class");
const elements = document.querySelectorAll(".class");

// Por ID (más rápido)
const el = document.getElementById("my-id");

// Con dataset
element.dataset.property = "value";
const value = element.dataset.property;

9. Manipulación del DOM

Crear elementos

// Crear elemento
const div = document.createElement("div");
div.className = "card";
div.textContent = "Contenido";

// Crear con innerHTML (solo si no hay datos de usuario)
container.innerHTML = '<div class="card">Contenido seguro</div>';

// Evitar XSS con textContent
const userInput = '<script>alert("xss")</script>';
element.textContent = userInput; // Seguro
element.innerHTML = userInput; // Peligroso

// insertAdjacentHTML (más eficiente que innerHTML)
container.insertAdjacentHTML("beforeend", '<div class="card">Nuevo</div>');
// Posiciones: "beforebegin", "afterbegin", "beforeend", "afterend"

Agregar elementos

// Append (al final)
parent.appendChild(element);
parent.append("Texto", element);

// Prepend (al inicio)
parent.prepend(element);

// Before/After
sibling.before(element);
sibling.after(element);

// Reemplazar
oldElement.replaceWith(newElement);

// Reemplazar todo el contenido
parent.replaceChildren(element1, element2);

// Remover
element.remove();

Templates

// Usar template para estructuras complejas
const template = document.querySelector("#card-template");
const card = template.content.cloneNode(true);
card.querySelector(".title").textContent = "Nuevo título";
document.querySelector(".container").appendChild(card);

ARIA properties modernas

// En lugar de setAttribute, usar propiedades aria directas
button.ariaLabel = "Cerrar";
button.ariaExpanded = "false";
button.ariaPressed = "true";
button.role = "tab";
button.tabIndex = 0;

// Popover API
element.showPopover();
element.hidePopover();
element.togglePopover();

10. Eventos

Event listener

// Basic
element.addEventListener("click", (event) => {
  console.log(event.target);
});

// Con opciones
element.addEventListener("click", handler, { once: true, passive: true });

// Cleanup con AbortSignal
const controller = new AbortController();
const { signal } = controller;

element.addEventListener("click", handler, { signal });
// Para remover todos los listeners asociados:
controller.abort();

Evitar onclick en HTML

<!-- ❌ Malo - JavaScript inline -->
<a onclick="guardar()">Guardar</a>
<button onclick="hacerAlgo()">Acción</button>

<!-- ✅ Bueno - Event listener -->
<button type="button" data-action="save">Guardar</button>
<button type="button" data-action="do-something">Acción</button>

<script>
  document.querySelectorAll("[data-action]").forEach((el) => {
    el.addEventListener("click", (e) => {
      // acción...
    });
  });
</script>

Event Delegation

// Un solo listener en el padre en lugar de uno por hijo
document.querySelector(".list")?.addEventListener("click", (event) => {
  const item = event.target.closest(".list__item");
  if (!item) return; // No era un item

  console.log("Item clicked:", item.dataset.id);
});

Pointer Events (unifican mouse + touch)

// Reemplaza mouse + touch events
element.addEventListener("pointerdown", (e) => {
  console.log(e.pointerType); // "mouse" | "touch" | "pen"
  console.log(e.clientX, e.clientY);
});

element.addEventListener("pointerup", handlePointerUp);
element.addEventListener("pointermove", handlePointerMove);

// CSS: touch-action: none si se va a prevenir scroll

Eventos de teclado

// Keyboard accessible
element.addEventListener("keydown", (event) => {
  if (event.key === "Enter" || event.key === " ") {
    event.preventDefault();
    // acción...
  }
});

11. Módulos ES (ESM)

Estructura de archivos

src/
├── js/
│   ├── main.js        # Entry point
│   ├── editor.js      # Lógica del editor
│   └── utils.js       # Funciones helper

Export/Import

// editor.js
export function initEditor() {
  // ...
}

export const EDITOR_CONFIG = {
  maxLength: 10000,
};

// main.js
import { initEditor } from "./editor.js";

document.addEventListener("DOMContentLoaded", () => {
  initEditor();
});

Dynamic import()

// Carga bajo demanda (código splitting)
const module = await import("./editor.js");
module.initEditor();

// Solo importar cuando se necesita
button.addEventListener("click", async () => {
  const { showToast } = await import("./toast.js");
  showToast("Guardado");
});

import.meta

// URL del módulo actual
console.log(import.meta.url);
// "https://ejemplo.com/js/utils.js"

// Metadatos (Vite: import.meta.env, import.meta.glob)
if (import.meta.env?.DEV) {
  console.log("Modo desarrollo");
}

Import Maps

<!-- Controlar resolución de módulos en HTML -->
<script type="importmap">
  {
    "imports": {
      "lodash-es": "/node_modules/lodash-es/lodash.js"
    }
  }
</script>
<script type="module">
  import { debounce } from "lodash-es";
</script>

12. Fetch API

Solicitud básica

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error("Fetch failed:", error);
    return null;
  }
}

Con headers

async function postData(url, data) {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer token123",
    },
    body: JSON.stringify(data),
  });
  return response.json();
}

Abort controller (cancelar petición)

const controller = new AbortController();
const signal = controller.signal;

fetch(url, { signal })
  .then((response) => response.json())
  .catch((error) => {
    if (error.name === "AbortError") {
      console.log("Request cancelled");
    }
  });

// Cancelar después de 5 segundos
setTimeout(() => controller.abort(), 5000);

Retry automático

async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Streaming (respuestas grandes)

// Leer respuesta como stream (sin esperar el body completo)
const response = await fetch("/large-file.json");
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value, { stream: true });
  // Procesar chunk progresivamente
}

// O usando Response.json() con reviver
const data = await response.json({
  reviver: (key, value, context) => {
    if (key === "timestamp") return new Date(value);
    return value;
  },
});

13. Programación Defensiva

Validar inputs

function processData(data) {
  if (!data || typeof data !== "object") {
    console.error("Invalid data");
    return null;
  }
  // procesar...
}

Optional chaining y nullish

// Optional chaining
const value = obj?.nested?.property;
const arr = data?.items?.[0];
const method = obj?.doSomething?.();

// Nullish coalescing
const name = user.name ?? "Anonymous";
const count = items.length ?? 0;

Validar elementos

// Verificar que elemento existe antes de usar
const button = document.querySelector(".btn-submit");
if (button) {
  button.addEventListener("click", handleSubmit);
}

14. Storage

localStorage

// Guardar
localStorage.setItem("user", JSON.stringify({ name: "John" }));

// Leer
const user = JSON.parse(localStorage.getItem("user") || "{}");

// Remover
localStorage.removeItem("user");

// Verificar disponibilidad
const hasStorage = () => {
  try {
    localStorage.setItem("test", "test");
    localStorage.removeItem("test");
    return true;
  } catch (e) {
    return false;
  }
};

sessionStorage

// Similar a localStorage pero se limpia al cerrar pestaña
sessionStorage.setItem("tempData", "value");
const temp = sessionStorage.getItem("tempData");

IndexedDB (wrapper promisificado)

// Wrapper funcional para operaciones comunes
const db = {
  async open(name, version = 1, upgrade) {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(name, version);
      request.onupgradeneeded = upgrade;
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  },

  async get(dbName, storeName, id) {
    const db = await this.open(dbName);
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readonly");
      const store = tx.objectStore(storeName);
      const request = store.get(id);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  },

  async set(dbName, storeName, value) {
    const db = await this.open(dbName);
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readwrite");
      const store = tx.objectStore(storeName);
      const request = store.put(value);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  },
};

// Uso
await db.set("myapp", "items", { id: 1, name: "test" });
const item = await db.get("myapp", "items", 1);

15. Date/Time (Intl API)

Formatear fechas

const date = new Date();

// Formato local
new Intl.DateTimeFormat("es-ES", {
  year: "numeric",
  month: "long",
  day: "numeric",
}).format(date);

// Formato corto
new Intl.DateTimeFormat("en-US", {
  month: "short",
  day: "numeric",
}).format(date);

Formatear números

// Moneda
new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR",
}).format(1234.56);

// Porcentaje
new Intl.NumberFormat("en-US", {
  style: "percent",
}).format(0.75);

RelativeTimeFormat

const rtf = new Intl.RelativeTimeFormat("es", { numeric: "auto" });

rtf.format(-1, "day"); // "ayer"
rtf.format(3, "month"); // "dentro de 3 meses"
rtf.format(-5, "minute"); // "hace 5 minutos"

16. Intersection Observer

Detectar cuando un elemento entra en el viewport:

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add("visible");
        // O cargar contenido lazy
      }
    });
  },
  {
    root: null,
    rootMargin: "0px",
    threshold: 0.1,
  },
);

observer.observe(document.querySelector(".lazy-load"));

17. Web Animations API

element.animate(
  [
    { opacity: 0, transform: "translateY(20px)" },
    { opacity: 1, transform: "translateY(0)" },
  ],
  {
    duration: 300,
    easing: "ease-out",
    fill: "forwards",
  },
);

18. APIs de Navegador Modernas

Clipboard API

// Escribir al portapapeles
await navigator.clipboard.writeText("Texto a copiar");

// Leer del portapapeles
const text = await navigator.clipboard.readText();

// Escribir imagen
const blob = new Blob(["<svg>...</svg>"], { type: "image/svg+xml" });
await navigator.clipboard.write([new ClipboardItem({ "image/svg+xml": blob })]);

Web Share API

// Compartir contenido (nativo del SO)
await navigator.share({
  title: "Mi artículo",
  text: "Mira esto",
  url: "https://ejemplo.com",
});

BroadcastChannel (comunicación entre tabs)

// Tab A — enviar
const channel = new BroadcastChannel("app-updates");
channel.postMessage({ type: "LOGOUT", userId: 42 });

// Tab B — recibir
const channel = new BroadcastChannel("app-updates");
channel.addEventListener("message", (event) => {
  if (event.data.type === "LOGOUT") {
    cerrarSesion();
  }
});

WebSocket

const ws = new WebSocket("wss://api.ejemplo.com/ws");

ws.addEventListener("open", () => {
  ws.send(JSON.stringify({ type: "join", room: "chat" }));
});

ws.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  mostrarMensaje(data);
});

ws.addEventListener("close", () => {
  console.log("Desconectado");
  // Reconexión automática después de 3s
  setTimeout(conectarWebSocket, 3000);
});

View Transition API

// Transiciones SPA sin librerías
document.addEventListener("click", async (e) => {
  const link = e.target.closest("a[data-transition]");
  if (!link) return;

  e.preventDefault();
  const url = link.href;

  // El navegador captura el estado actual
  const transition = document.startViewTransition(async () => {
    const html = await fetch(url).then((r) => r.text());
    document.body.innerHTML = html;
  });

  await transition.finished;
});

Popover API

// Controlar popover desde JS
const popover = document.getElementById("menu-popover");
popover.showPopover();
popover.hidePopover();
popover.togglePopover();

// Cerrar con hidePopover() desde cualquier evento
popover.addEventListener("beforetoggle", (e) => {
  if (e.newState === "open") {
    console.log("Popover abierto");
  }
});

File System Access API

// Abrir selector de archivos y leer
const [handle] = await window.showOpenFilePicker();
const file = await handle.getFile();
const content = await file.text();

// Guardar archivo
const saveHandle = await window.showSaveFilePicker();
const writable = await saveHandle.createWritable();
await writable.write("Contenido del archivo");
await writable.close();

19. Performance

Evitar Reflows/Repaints

// ❌ Malo - múltiples reflows
element.style.width = "100px";
element.style.height = "100px";
element.style.padding = "10px";

// ✅ Bueno - usar CSS classes o cssText
element.style.cssText = "width: 100px; height: 100px; padding: 10px;";

// ✅ Mejor - usar classList
element.classList.add("active");

requestIdleCallback (tareas no críticas)

// Ejecutar cuando el navegador esté inactivo
requestIdleCallback(
  (deadline) => {
    while (deadline.timeRemaining() > 0 && tasks.length > 0) {
      processTask(tasks.shift());
    }
    if (tasks.length > 0) {
      requestIdleCallback(processTasks); // Continuar después
    }
  },
  { timeout: 2000 },
);

requestAnimationFrame (animaciones JS necesarias)

// Animación fluida con delta time
let start = null;

function animate(timestamp) {
  if (!start) start = timestamp;
  const progress = timestamp - start;

  element.style.transform = `translateX(${Math.min(progress * 0.1, 200)}px)`;

  if (progress < 2000) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

Debounce

function debounce(fn, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

// Uso
const debouncedSearch = debounce(search, 300);
input.addEventListener("input", debouncedSearch);

Throttle

function throttle(fn, limit) {
  let inThrottle;
  return (...args) => {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

// Uso
const throttledScroll = throttle(handleScroll, 100);
window.addEventListener("scroll", throttledScroll);

20. Prohibiciones

  • NO usar: Vue, Svelte, Angular (sin autorización)
  • NO usar: React (sin cargar skill react)
  • NO usar: TypeScript para vanilla (sin cargar skill typescript)
  • ❌ No usar var
  • ❌ No dejar console.log, debugger en código final
  • ❌ No usar jQuery para selección básica (usar vanilla)
  • ❌ No usar JavaScript para animaciones simples (usar CSS)
  • ❌ No usar onclick en HTML (usar addEventListener)
  • ❌ No usar innerHTML con datos de usuario (riesgo XSS)
  • ❌ No usar JSON.parse(JSON.stringify(obj)) para clonar (usar structuredClone)
  • ❌ No anidar Promise constructors si se puede usar Promise.withResolvers()

21. Dependencias Comunes

JavaScript vanilla típicamente no requiere dependencias. Para proyectos específicos, considerar:

PaqueteUso
date-fnsFechas (si Intl.DateTimeFormat no basta)
idbIndexedDB con promesas (wrapper)

Nota: Con structuredClone(), Object.groupBy(), Set operations e Iterator helpers, lodash ya no es necesario en la mayoría de proyectos vanilla.


22. Referencias

Nota: Para HTML semántico, ver HTML Nota: Para estilos CSS, ver CSS Nota: Para despliegue, ver Deploy


Última actualización: 2026-07

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.