miércoles, 19 de noviembre de 2025

Menú con submenús

 Quiero hacer un área dentro de una página web donde al clickear tanto con el botón derecho como con el izquierdo aparezca un menú de items personalizado.

Perfecto, Gabriel 🙌. Lo que quieres es crear un menú contextual personalizado que aparezca al hacer clic (izquierdo o derecho) dentro de un área específica de tu página. Te muestro cómo hacerlo paso a paso con HTML, CSS y JavaScript.

📂 Ejemplo básico

HTML

html
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Menú personalizado</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div id="area">
    Haz clic aquí (izquierdo o derecho)
  </div>

  <!-- Menú oculto -->
  <ul id="menu">
    <li>Opción 1</li>
    <li>Opción 2</li>
    <li>Opción 3</li>
  </ul>

  <script src="script.js"></script>
</body>
</html>

CSS (styles.css)

css
#area {
  width: 300px;
  height: 200px;
  border: 2px dashed #007bff;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 50px auto;
  cursor: pointer;
}

#menu {
  position: absolute;
  list-style: none;
  margin: 0;
  padding: 5px;
  background: white;
  border: 1px solid #ccc;
  display: none; /* oculto por defecto */
}

#menu li {
  padding: 8px 12px;
  cursor: pointer;
}

#menu li:hover {
  background: #007bff;
  color: white;
}

JavaScript (script.js)

js
const area = document.getElementById("area");
const menu = document.getElementById("menu");

// Mostrar menú en clic izquierdo o derecho
area.addEventListener("click", mostrarMenu);
area.addEventListener("contextmenu", mostrarMenu);

// Ocultar menú al hacer clic fuera
document.addEventListener("click", (e) => {
  if (!menu.contains(e.target) && e.target !== area) {
    menu.style.display = "none";
  }
});

function mostrarMenu(e) {
  e.preventDefault(); // evita menú por defecto en clic derecho
  menu.style.display = "block";
  menu.style.left = e.pageX + "px";
  menu.style.top = e.pageY + "px";
}

🔹 ¿Qué hace este código?

  • contextmenu captura el clic derecho y evita el menú del navegador.

  • click captura el clic izquierdo.

  • Ambos llaman a mostrarMenu, que posiciona el menú en las coordenadas del clic.

  • El menú se oculta si haces clic fuera de él.

✨ Posibles mejoras

  • Cambiar el contenido del menú según el contexto (ej. diferentes opciones si clicas en distintos elementos).

  • Animaciones para mostrar/ocultar el menú.

  • Integrar con frameworks como React o Vue para hacerlo más dinámico.

¿Quieres que te prepare una versión mejorada con animaciones y diferentes opciones según el botón (izquierdo vs derecho) para que veas cómo se puede sofisticar este menú?

Quiero que el menú funcione cuando se clickea sobre un SVG, dependiendo de qué elemento se clickea, pero que también funcione si el click es en un área vacía del SVG.

Perfecto, Gabriel 🙌. Lo que planteas es un caso muy interesante: un menú contextual dinámico sobre un SVG, que muestre opciones distintas según el elemento clickeado, pero que también tenga un menú por defecto si se hace clic en un área vacía del SVG.

📂 Ejemplo completo

HTML

html
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Menú contextual en SVG</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <svg id="miSVG" width="400" height="300" style="border:1px solid #ccc;">
    <!-- Elementos dentro del SVG -->
    <circle id="circulo" cx="100" cy="150" r="40" fill="skyblue" />
    <rect id="rectangulo" x="200" y="100" width="80" height="80" fill="tomato" />
  </svg>

  <!-- Menú contextual -->
  <ul id="menu">
    <!-- Items se llenan dinámicamente -->
  </ul>

  <script src="script.js"></script>
</body>
</html>

CSS (styles.css)

css
#menu {
  position: absolute;
  list-style: none;
  margin: 0;
  padding: 5px;
  background: white;
  border: 1px solid #ccc;
  display: none;
  min-width: 120px;
}

#menu li {
  padding: 8px 12px;
  cursor: pointer;
}

#menu li:hover {
  background: #007bff;
  color: white;
}

JavaScript (script.js)

js
const svg = document.getElementById("miSVG");
const menu = document.getElementById("menu");

// Definimos menús distintos según el target
const menus = {
  circulo: ["Editar círculo", "Eliminar círculo"],
  rectangulo: ["Editar rectángulo", "Eliminar rectángulo"],
  default: ["Agregar elemento", "Propiedades generales"]
};

// Mostrar menú contextual
svg.addEventListener("contextmenu", (e) => {
  e.preventDefault();

  // Determinar sobre qué se hizo clic
  const targetId = e.target.id;
  const opciones = menus[targetId] || menus.default;

  // Limpiar menú anterior
  menu.innerHTML = "";

  // Crear items dinámicamente
  opciones.forEach(opcion => {
    const li = document.createElement("li");
    li.textContent = opcion;
    li.addEventListener("click", () => {
      alert(`Acción: ${opcion}`);
      menu.style.display = "none";
    });
    menu.appendChild(li);
  });

  // Posicionar menú en coordenadas del clic
  menu.style.display = "block";
  menu.style.left = e.pageX + "px";
  menu.style.top = e.pageY + "px";
});

// Ocultar menú al hacer clic fuera
document.addEventListener("click", (e) => {
  if (!menu.contains(e.target)) {
    menu.style.display = "none";
  }
});

🔹 ¿Qué hace este código?

  • Captura el evento contextmenu (clic derecho) sobre el SVG.

  • Identifica si el clic fue sobre un elemento (circle, rect) o sobre el área vacía del SVG.

  • Según el id del elemento clickeado, carga un menú distinto.

  • Si el clic es en un área vacía, muestra el menú por defecto.

  • Los items del menú se crean dinámicamente y pueden ejecutar acciones distintas.

✨ Posibles mejoras

  • Hacer que también funcione con clic izquierdo (click) además del derecho.

  • Añadir animaciones para mostrar/ocultar el menú.

  • Cambiar el contenido del menú según atributos del elemento (ej. color, tamaño).

  • Integrar con React/Vue para manejar estados más complejos.

¿Quieres que te prepare una versión extendida donde el menú cambie también con clic izquierdo y derecho (menús distintos según el botón)?

Lo quiero para usar con React y Tailwind.

Perfecto, Gabriel 🙌. Te muestro cómo implementar un menú contextual dinámico sobre un SVG en React con TailwindCSS, que cambia según el elemento clickeado (círculo, rectángulo, o área vacía).

📂 Ejemplo con React + Tailwind

App.jsx

jsx
import { useState } from "react";

export default function App() {
  const [menu, setMenu] = useState([]);
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const [visible, setVisible] = useState(false);

  const menus = {
    circulo: ["Editar círculo", "Eliminar círculo"],
    rectangulo: ["Editar rectángulo", "Eliminar rectángulo"],
    default: ["Agregar elemento", "Propiedades generales"],
  };

  const handleContextMenu = (e) => {
    e.preventDefault();

    // Detectar el target dentro del SVG
    const targetId = e.target.id;
    const opciones = menus[targetId] || menus.default;

    setMenu(opciones);
    setPos({ x: e.pageX, y: e.pageY });
    setVisible(true);
  };

  const handleClickOutside = () => {
    setVisible(false);
  };

  return (
    <div className="w-full h-screen bg-gray-100" onClick={handleClickOutside}>
      <svg
        id="miSVG"
        width="400"
        height="300"
        className="border border-gray-400 mx-auto mt-10"
        onContextMenu={handleContextMenu}
      >
        <circle id="circulo" cx="100" cy="150" r="40" fill="skyblue" />
        <rect id="rectangulo" x="200" y="100" width="80" height="80" fill="tomato" />
      </svg>

      {visible && (
        <ul
          className="absolute bg-white border border-gray-300 rounded shadow-md min-w-[150px] z-50"
          style={{ top: pos.y, left: pos.x }}
        >
          {menu.map((item, idx) => (
            <li
              key={idx}
              className="px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer"
              onClick={() => {
                alert(`Acción: ${item}`);
                setVisible(false);
              }}
            >
              {item}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

🔹 ¿Qué hace este código?

  • Usa React state para manejar:

    • menu: las opciones actuales.

    • pos: coordenadas del clic.

    • visible: si el menú está abierto o no.

  • Captura el evento onContextMenu en el SVG.

  • Según el id del elemento clickeado (circulo, rectangulo o vacío), carga un menú distinto.

  • Renderiza el menú en la posición del clic usando estilos de TailwindCSS.

  • Se oculta al hacer clic fuera.

✨ Mejoras posibles

  • Diferenciar entre clic izquierdo y derecho (onClick vs onContextMenu).

  • Animaciones con Tailwind (transition, transform, scale).

  • Menús más complejos con subitems o íconos.

  • Integración con lógica real (ej. editar/eliminar elementos del SVG).

Así tienes un menú contextual totalmente dinámico y estilizado con Tailwind, que funciona tanto sobre elementos del SVG como en áreas vacías.

¿Quieres que te prepare también una versión con menús distintos para clic izquierdo y clic derecho sobre el SVG?

Además quiero que el menú tenga submenús, y que el proyecto esté estructurado para Vite.

Perfecto, Gabriel 🙌. Vamos a armar un mini proyecto con Vite + React + TailwindCSS que tenga un menú contextual con submenús sobre un SVG. Así tendrás la estructura clara y el código listo para expandir.

📂 Estructura del proyecto con Vite

Código
mi-proyecto/
├── index.html
├── package.json
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
├── /src
│   ├── main.jsx
│   ├── App.jsx
│   ├── /components
│   │   ├── ContextMenu.jsx
│   │   └── SubMenu.jsx
│   ├── /styles
│   │   └── index.css
│   └── /assets
│       └── logo.svg

🔹 Configuración inicial

  1. Crear proyecto con Vite:

    bash
    npm create vite@latest mi-proyecto -- --template react
    cd mi-proyecto
    
  2. Instalar TailwindCSS:

    bash
    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init -p
    
  3. Configurar tailwind.config.js:

    js
    export default {
      content: ["./index.html", "./src/**/*.{js,jsx}"],
      theme: {
        extend: {},
      },
      plugins: [],
    };
    
  4. Importar Tailwind en src/styles/index.css:

    css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    

🔹 Componente de Menú con Submenús

ContextMenu.jsx

jsx
import { useState } from "react";
import SubMenu from "./SubMenu";

export default function ContextMenu({ items, pos, visible, onClose }) {
  if (!visible) return null;

  return (
    <ul
      className="absolute bg-white border border-gray-300 rounded shadow-md min-w-[150px] z-50"
      style={{ top: pos.y, left: pos.x }}
    >
      {items.map((item, idx) => (
        <li
          key={idx}
          className="relative px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer group"
          onClick={() => {
            if (!item.submenu) {
              alert(`Acción: ${item.label}`);
              onClose();
            }
          }}
        >
          {item.label}
          {item.submenu && (
            <SubMenu items={item.submenu} />
          )}
        </li>
      ))}
    </ul>
  );
}

SubMenu.jsx

jsx
export default function SubMenu({ items }) {
  return (
    <ul className="absolute left-full top-0 bg-white border border-gray-300 rounded shadow-md min-w-[150px] hidden group-hover:block">
      {items.map((subItem, idx) => (
        <li
          key={idx}
          className="px-4 py-2 hover:bg-green-500 hover:text-white cursor-pointer"
          onClick={() => alert(`Acción: ${subItem.label}`)}
        >
          {subItem.label}
        </li>
      ))}
    </ul>
  );
}

App.jsx

jsx
import { useState } from "react";
import ContextMenu from "./components/ContextMenu";

export default function App() {
  const [menuItems, setMenuItems] = useState([]);
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const [visible, setVisible] = useState(false);

  const menus = {
    circulo: [
      { label: "Editar círculo" },
      { label: "Más opciones", submenu: [
        { label: "Cambiar color" },
        { label: "Cambiar tamaño" }
      ]}
    ],
    rectangulo: [
      { label: "Editar rectángulo" },
      { label: "Más opciones", submenu: [
        { label: "Rotar" },
        { label: "Duplicar" }
      ]}
    ],
    default: [
      { label: "Agregar elemento" },
      { label: "Propiedades generales" }
    ]
  };

  const handleContextMenu = (e) => {
    e.preventDefault();
    const targetId = e.target.id;
    const opciones = menus[targetId] || menus.default;
    setMenuItems(opciones);
    setPos({ x: e.pageX, y: e.pageY });
    setVisible(true);
  };

  const handleClickOutside = () => setVisible(false);

  return (
    <div className="w-full h-screen bg-gray-100" onClick={handleClickOutside}>
      <svg
        id="miSVG"
        width="400"
        height="300"
        className="border border-gray-400 mx-auto mt-10"
        onContextMenu={handleContextMenu}
      >
        <circle id="circulo" cx="100" cy="150" r="40" fill="skyblue" />
        <rect id="rectangulo" x="200" y="100" width="80" height="80" fill="tomato" />
      </svg>

      <ContextMenu
        items={menuItems}
        pos={pos}
        visible={visible}
        onClose={handleClickOutside}
      />
    </div>
  );
}

✨ Resultado

  • Al hacer clic derecho sobre el círculo o el rectángulo, aparece un menú con opciones específicas.

  • Algunos ítems tienen submenús que se despliegan al pasar el mouse.

  • Si haces clic en un área vacía del SVG, aparece el menú por defecto.

  • Todo estilizado con TailwindCSS y estructurado para un proyecto con Vite.

¿Quieres que te prepare también una versión con menús distintos para clic izquierdo y derecho (ej. menú de edición vs menú de acciones rápidas)?

martes, 18 de noviembre de 2025

Bundlers de frontend (HTML, JS, CSS, assets)

 ¿Qué información importante hay sobre estructuras de proyectos frontend y bundlers para gente que no conoce nada de eso?


Respuesta breve: Una estructura de proyecto frontend organiza los archivos y carpetas para que el desarrollo sea claro y mantenible. Los bundlers (como Vite, Webpack o Parcel) son herramientas que empaquetan tu código y recursos (HTML, CSS, JS, imágenes) para que el navegador los entienda y cargue más rápido.

🧩 Estructura básica de un proyecto frontend

Para alguien que empieza desde cero, lo más importante es entender cómo se organizan los archivos:

  • index.html → El punto de entrada de la aplicación. Aquí se cargan los estilos y scripts.

  • src/ → Carpeta donde vive tu código fuente.

    • components/ → Pequeñas piezas reutilizables de interfaz (ej. botones, menús).

    • styles/ → Archivos CSS o preprocesadores (Sass, Less).

    • assets/ → Imágenes, íconos, fuentes.

    • main.js o main.tsx → Archivo principal que inicia la aplicación.

  • public/ → Archivos estáticos que no necesitan procesamiento (ej. favicon).

  • package.json → Lista de dependencias y scripts para automatizar tareas.

👉 Esta organización ayuda a que el proyecto sea legible, escalable y fácil de mantener.

⚙️ ¿Qué es un bundler?

Un bundler es como una “máquina de empaquetado” para tu proyecto:

  • Agrupa archivos: Combina todos tus módulos JS en un solo archivo optimizado.

  • Optimiza recursos: Comprime imágenes, minifica CSS y JS para que pesen menos.

  • Traduce código moderno: Convierte sintaxis nueva (ES6+, TypeScript, JSX) en algo que todos los navegadores entienden.

  • Carga más rápida: Divide el código en partes (code splitting) para que el navegador solo cargue lo necesario.

Ejemplos de bundlers populares

BundlerCaracterísticas principales
WebpackMuy flexible, usado en proyectos grandes, requiere configuración detallada.
ParcelCasi sin configuración, ideal para principiantes.
ViteSúper rápido, usa ESBuild, pensado para frameworks modernos como React y Vue.

🌱 Por qué son importantes para principiantes

  • Sin bundler: Tendrías que enlazar manualmente cada archivo JS y CSS en tu HTML. Esto se vuelve caótico en proyectos grandes.

  • Con bundler: Todo se automatiza. Puedes escribir código moderno y el bundler se encarga de que funcione en cualquier navegador.

  • Estructura clara + bundler = proyectos más fáciles de escalar y mantener.

📌 Conclusión

Para alguien que recién empieza:

  1. Aprende la estructura básica de carpetas y archivos.

  2. Usa un bundler sencillo como Vite o Parcel para no complicarte con configuraciones.

  3. Piensa en modularidad: divide tu código en componentes y organiza tus estilos y recursos.

Esto te dará una base sólida para crecer en el mundo del frontend.

Fuentes:

  • KeepCoding: Estructura de un proyecto frontend

  • Kranio: Guía básica para iniciarte en frontend


Quiero ver un ejemplo del enlace de JS y CSS sin bundler y con bundler.

Te muestro dos escenarios muy claros: cómo se enlazan archivos sin bundler (todo manual) y cómo se hace con bundler (ejemplo con Vite).

🔹 Ejemplo sin bundler

Aquí todo se conecta directamente en el index.html. Tú mismo enlazas cada archivo JS y CSS:

html
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8" />
  <title>Proyecto sin bundler</title>
  <!-- Enlace directo al CSS -->
  <link rel="stylesheet" href="styles/main.css" />
</head>
<body>
  <h1>Hola mundo</h1>

  <!-- Enlace directo a cada archivo JS -->
  <script src="js/utils.js"></script>
  <script src="js/app.js"></script>
</body>
</html>
  • Si tienes 10 archivos JS, debes enlazar los 10 manualmente.

  • El navegador carga cada archivo por separado, lo que puede ser más lento.

  • No hay optimización automática: si usas sintaxis moderna (ES6, JSX, TypeScript), puede que no funcione en todos los navegadores.

🔹 Ejemplo con bundler (Vite)

Con un bundler, tu index.html solo necesita un punto de entrada. El bundler se encarga de empaquetar y optimizar todo:

html
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8" />
  <title>Proyecto con Vite</title>
</head>
<body>
  <div id="root"></div>
  <!-- Vite inyecta automáticamente el JS y CSS procesado -->
  <script type="module" src="/src/main.js"></script>
</body>
</html>
  • En src/main.js importas todo lo que necesites:

    js
    import "./styles/main.css";
    import { initApp } from "./app.js";
    
    initApp();
    
  • Vite se encarga de:

    • Convertir tu código moderno en algo que todos los navegadores entiendan.

    • Optimizar y agrupar los archivos en un solo paquete.

    • Inyectar automáticamente los estilos y scripts en el HTML final.

✨ Diferencia clave

  • Sin bundler: Tú gestionas manualmente los enlaces.

  • Con bundler: Solo declaras tus imports en el código, y el bundler hace la magia de empaquetar y optimizar.

Esto es como la diferencia entre cocinar todo desde cero (sin bundler) y usar una cocina profesional que ya te organiza ingredientes y tiempos (con bundler).


¿Quieres que te arme un mini proyecto de ejemplo con Vite (estructura de carpetas y archivos) para que veas cómo se organiza todo en la práctica?

Sí.

Genial, Gabriel 🙌. Te muestro cómo se vería un mini proyecto con Vite para que tengas una idea clara de la estructura y cómo se organiza todo.

📂 Estructura mínima de un proyecto con Vite + React

Código
mi-proyecto/
├── index.html
├── package.json
├── vite.config.js
├── /public
│   └── favicon.ico
└── /src
    ├── main.jsx
    ├── App.jsx
    ├── /components
    │   └── Boton.jsx
    ├── /styles
    │   └── main.css
    └── /assets
        └── logo.svg

🔹 Archivos principales

index.html

El punto de entrada, muy simple porque Vite se encarga de inyectar los recursos:

html
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <title>Mi Proyecto con Vite</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

src/main.jsx

Aquí arrancamos la aplicación:

jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./styles/main.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

src/App.jsx

Un componente principal que usa otro componente y un SVG:

jsx
import Boton from "./components/Boton.jsx";
import Logo from "./assets/logo.svg?react";

export default function App() {
  return (
    <div>
      <Logo width={80} height={80} />
      <h1>Hola Gabriel 👋</h1>
      <Boton texto="Haz clic aquí" />
    </div>
  );
}

src/components/Boton.jsx

Un componente reutilizable:

jsx
export default function Boton({ texto }) {
  return <button>{texto}</button>;
}

src/styles/main.css

Tus estilos globales:

css
body {
  font-family: sans-serif;
  background-color: #f5f5f5;
  text-align: center;
}
button {
  padding: 10px 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
}

✨ Lo que hace Vite por ti

  • Empaqueta todo automáticamente.

  • Convierte el SVG en un componente React gracias a vite-plugin-svgr.

  • Optimiza los estilos y scripts para producción.

  • Te da un servidor de desarrollo rápido con hot reload.

Así, en lugar de enlazar manualmente cada archivo como en el ejemplo sin bundler, solo importas lo que necesitas en tu código y Vite se encarga del resto.

¿Quieres que te muestre también cómo se vería el build final (los archivos que Vite genera para producción)?

¿El CSS no debería ser importado desde el index.html, puesto que afecta a elementos de este HTML como ser el body?

Muy buena observación 👌. La diferencia está en cómo trabajan los bundlers como Vite frente a un proyecto sin bundler:

🔹 Sin bundler

  • El navegador solo entiende enlaces directos en el index.html.

  • Por eso, si quieres que el body tenga estilos, debes poner:

html
<link rel="stylesheet" href="styles/main.css" />

El navegador carga ese CSS y lo aplica a todo el documento.

🔹 Con bundler (ej. Vite)

  • El index.html ya no es el lugar donde gestionas manualmente todos los recursos.

  • En su lugar, importas el CSS dentro de tu código JavaScript (main.jsx o main.js):

js
import "./styles/main.css";
  • Vite detecta ese import y lo inyecta automáticamente en el HTML final cuando construye el proyecto.

  • Así, aunque el CSS afecte al body, no necesitas enlazarlo manualmente en el index.html. El bundler se encarga de que esté disponible globalmente.

✨ Ventaja de este enfoque

  • Modularidad: cada componente puede importar su propio CSS, y Vite lo combina todo.

  • Optimización: el bundler minifica y agrupa los estilos para producción.

  • Simplicidad: solo declaras imports en tu código, y no te preocupas por el orden de <link> en el HTML.

👉 En resumen:

  • Sin bundler: enlazas CSS en el index.html.

  • Con bundler: lo importas en tu JS, y el bundler lo inyecta en el HTML final.

¿Quieres que te muestre cómo se ve el HTML final que genera Vite en producción, para que compares con el index.html inicial?

Quiero ver el build final (los archivos que Vite genera para producción)

Perfecto, Gabriel 🙌. Te muestro cómo se ve el resultado del build final cuando corres vite build.

📂 Estructura típica del build de Vite

Cuando ejecutas:

bash
npm run build

Vite genera una carpeta llamada dist/ con los archivos optimizados listos para producción:

Código
dist/
├── index.html
├── assets/
│   ├── index-abc123.js
│   ├── index-def456.css
│   ├── logo-789ghi.svg
│   └── vendor-xyz987.js

🔹 index.html final

El HTML ya no tiene imports directos a tus archivos fuente (src/). En su lugar, apunta a los archivos procesados y minificados en dist/assets:

html
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <title>Mi Proyecto con Vite</title>
    <link rel="stylesheet" href="/assets/index-def456.css">
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/assets/index-abc123.js"></script>
  </body>
</html>

🔹 Archivos generados

  • index-abc123.js → Tu código JavaScript empaquetado y minificado.

  • vendor-xyz987.js → Dependencias externas (React, librerías).

  • index-def456.css → Tus estilos combinados y optimizados.

  • logo-789ghi.svg → Tus imágenes y assets, renombrados con hashes para cacheo.

✨ ¿Por qué se ve así?

  • Los nombres de archivo incluyen un hash único (abc123, def456) para evitar problemas de caché en el navegador.

  • Todo el CSS se agrupa en un archivo optimizado.

  • El JS se divide en chunks (ej. index.js y vendor.js) para cargar más rápido.

  • El HTML final es muy simple: solo referencia los archivos optimizados.

En resumen:

  • Durante desarrollo → Importas CSS y JS directamente en tu código (main.jsx).

  • En producción → Vite genera un dist/ con HTML limpio y archivos optimizados con nombres únicos.

¿Quieres que te arme un ejemplo visual comparando lado a lado el index.html de desarrollo vs el index.html de producción para que veas la diferencia más clara?

Hoy
¿No viola la modularidad el hecho de que un CSS importado desde un archivo que no es el HTML termine afectando a elementos de este?

Excelente pregunta, Gabriel 👌. La clave está en entender qué significa modularidad en frontend y cómo los bundlers (como Vite) manejan los estilos.

🔹 Modularidad en CSS tradicional

  • Si enlazas un CSS en el index.html, ese archivo afecta a todo el documento.

  • No hay separación clara: cualquier regla puede impactar cualquier elemento.

  • Esto es lo que suele llamarse CSS global.

🔹 Modularidad con bundlers

Cuando importas un CSS desde un archivo JS/TS (ej. main.jsx):

js
import "./styles/main.css";
  • El bundler no rompe la modularidad.

  • Lo que hace es inyectar ese CSS en el documento final, igual que si lo hubieras puesto en el <link> del HTML.

  • La diferencia es que ahora el CSS forma parte del grafo de dependencias:

    • Si eliminas el import, ese CSS ya no se incluye en el build.

    • Puedes importar CSS específico en cada componente, y el bundler lo agrupa y optimiza.

🔹 Opciones para modularidad real

Si quieres que un CSS no afecte globalmente al body o a otros elementos, tienes varias estrategias:

  • CSS Modules:

    jsx
    import styles from "./Boton.module.css";
    
    export default function Boton() {
      return <button className={styles.primary}>Haz clic</button>;
    }
    

    → Aquí las clases se convierten en nombres únicos, evitando colisiones.

  • Scoped styles (ej. Vue, Svelte): Los estilos se limitan al componente.

  • CSS-in-JS (ej. styled-components, Emotion): Los estilos se definen dentro del propio componente y se aplican solo allí.

✨ Conclusión

  • Importar CSS desde JS no viola modularidad: simplemente cambia el punto de entrada del estilo (del HTML al grafo de dependencias).

  • Lo que sí viola modularidad es usar CSS global sin control, porque afecta a todo el documento.

  • Bundlers te dan herramientas (CSS Modules, CSS-in-JS) para mantener modularidad y evitar que un estilo de un componente se filtre a otros.

En otras palabras: el import en JS es solo un mecanismo técnico, la modularidad depende de cómo organices y limites tus estilos.

¿Quieres que te muestre un ejemplo comparando un CSS global vs un CSS Module en Vite para que veas la diferencia práctica?

domingo, 16 de noviembre de 2025

Diferentes maneras de importar un archivo .svg

 Estoy importando un archivo que se llama torta.svg como si fuera un componente de React, a través de SVGR. Dice: InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('http://127.0.0.1:5173/resources/images/torta.svg') is not a valid name.


Este es un error muy común cuando se usa SVGR y Vite, ¡pero es fácil de solucionar!

El mensaje de error indica que React está intentando usar una cadena de texto que contiene la ruta de tu archivo SVG (http://127.0.0.1:5173/.../torta.svg) como si fuera el nombre de un componente JSX.

Esto sucede porque Vite y SVGR exponen el SVG de dos maneras distintas, y probablemente estás usando la exportación por defecto, que es la URL del archivo, en lugar de la exportación nombrada, que es el componente React en sí.

La Solución: Usar la Exportación Nombrada (ReactComponent)

Cuando instalaste vite-plugin-svgr, este configuró la importación para darte dos cosas:

  1. Exportación por defecto: La URL/ruta del archivo (un string). Útil si quieres pasarla a un <img> o una función de fondo CSS.

  2. Exportación nombrada (ReactComponent): El componente funcional de React (la función que devuelve JSX). Esto es lo que quieres.

Debes cambiar cómo importas el archivo, utilizando un alias para ReactComponent:

Aquí tienes un ejemplo del componente corrigiendo el error:

Uso Correcto de SVG con SVGR

import React from 'react';
// IMPORTACIÓN INCORRECTA (Genera la URL/ruta como string):
// import Torta from './torta.svg';

// IMPORTACIÓN CORRECTA (Usa la exportación nombrada ReactComponent y le asigna el alias Torta).
// NOTA: Se ha corregido la ruta. Asegúrate de que esta ruta (../assets/torta.svg) sea correcta respecto a tu archivo torta.svg real.
import { ReactComponent as Torta } from '../assets/torta.svg';

const TortaComponente = () => {
return (
<div className="flex flex-col items-center justify-center p-8 bg-white rounded-xl shadow-lg m-4">
<h1 className="text-3xl font-bold text-gray-800 mb-4">¡Feliz Día!</h1>
{/* AQUÍ SE USA EL COMPONENTE:
Ahora <Torta /> es una función de React, no una cadena de texto.
Le podemos pasar props como className, fill, width, etc.
*/}
<Torta
className="transition-transform duration-300 hover:scale-110"
width={150}
height={150}
fill="#ff9a8d" // Un color de pastel bonito
/>

<p className="mt-4 text-gray-600">Este SVG ahora se comporta como un componente React nativo.</p>
</div>
);
};

export default TortaComponente;

Resumen de la Corrección

Asegúrate de que tu línea de importación se vea así:

JavaScript
import { ReactComponent as NombreDeseado } from './ruta/al/archivo.svg';

Todavía no entiendo qué estoy haciendo exactamente al transformar un SVG en un componente de React.

Entiendo perfectamente tu confusión. Es uno de los conceptos más difíciles de entender cuando se empieza a trabajar con React y herramientas de bundling como Vite o Webpack, especialmente en cómo manejan los archivos que no son JavaScript (como los SVG).

La clave está en que el sistema de importación moderno (import ... from '...') tiene múltiples propósitos.

Aquí te explico la diferencia fundamental entre importar un SVG como URL y como componente, que es la función de SVGR.

Voy a generar un archivo Markdown con la explicación.

Explicación: SVG como Componente React

El Misterio de la Doble Importación de SVG

Cuando importas un archivo en un proyecto de React moderno, el bundler (Vite, en este caso) lo procesa según su tipo. Para un SVG, el bundler ofrece dos "sabores" de importación, y SVGR es el responsable de crear el segundo.

1. La Importación por Defecto (El Error que Tuviste)

Cuando escribes:

import Torta from './torta.svg';
// Torta === "[http://127.0.0.1:5173/resources/images/torta.svg](http://127.0.0.1:5173/resources/images/torta.svg)"

¿Qué pasa? El bundler interpreta el SVG como un activo estático (una imagen o archivo de recursos). Su valor por defecto es la URL pública donde se encontrará ese archivo en el navegador.

¿Por qué falla? React ve la importación Torta, cuyo valor es una cadena de texto (ej. "http://..."), y asume que quieres crear un elemento HTML con ese nombre de etiqueta: <http://.../>. Como la URL no es un nombre de etiqueta HTML válido (<div>, <span>, etc.), lanza el error InvalidCharacterError.

Uso Correcto de la URL: Este método solo sirve para usar el SVG como una fuente de imagen tradicional:

<img src={Torta} alt="Una torta" /> 
// o en CSS: background-image: url(${Torta})

2. La Importación Nombrada ReactComponent (La Solución)

Aquí es donde interviene SVGR. SVGR es un cargador (loader) que, antes de que el bundler lo convierta en una URL, lo lee y hace lo siguiente:

  1. Transformación a JSX: Convierte el código XML del SVG en una estructura de función que devuelve JSX.

  2. Exportación Nombrada: Exporta esta función de componente bajo el nombre ReactComponent.

Cuando escribes:

import { ReactComponent as Torta } from './torta.svg';
// Torta === función (props) => { return <svg>...</svg> }

¿Qué pasa? Al usar import { ReactComponent as Torta }, estás diciendo: "No quiero la URL por defecto. Quiero la función componente que generó SVGR, y la llamaré Torta."

¿Por qué funciona? Ahora, Torta es una función válida de React. Cuando la usas en JSX como <Torta />, React la invoca, renderiza el código SVG directamente en el DOM y, lo más importante, puedes controlar sus propiedades (props).

Beneficios de usar SVG como Componente

Característica

Importación como URL (<img>)

Importación como Componente (<Torta />)

Control de Estilo

Limitado. Solo width, height.

Total. Puedes pasar className, fill, stroke para cambiar colores o animaciones con CSS.

Interactividad

Ninguna. Es una imagen estática.

Total. Puedes agregar onClick a partes del SVG, manejar eventos del mouse, etc.

Rendimiento

Se carga como un recurso externo (petición HTTP).

Se inyecta directamente en el DOM (código en línea). Más rápido y evita peticiones adicionales.

Espero que esta explicación aclare por qué necesitas usar esa sintaxis de importación específica (import { ReactComponent as Torta } from '...') cuando quieres que tu SVG se comporte como un elemento nativo y manipulable dentro de tu aplicación React.



¿Cómo se usa SVGR con Vite?

Respuesta rápida: Para usar SVGR con Vite necesitas instalar el plugin vite-plugin-svgr, configurarlo en tu vite.config.js, y luego importar tus archivos .svg como componentes de React usando la sintaxis ?react npm DEV Community.


🚀 Pasos detallados para usar SVGR con Vite

1. Instalar el plugin

Puedes hacerlo con tu gestor de paquetes favorito:

  • npm
    npm install --save-dev vite-plugin-svgr
    
  • yarn
    yarn add -D vite-plugin-svgr
    
  • pnpm
    pnpm add -D vite-plugin-svgr
    

2. Configurar Vite

En tu archivo vite.config.js agrega el plugin:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";

export default defineConfig({
  plugins: [react(), svgr()],
});

3. Importar SVGs como componentes

Una vez configurado, puedes importar tus SVGs como componentes de React:

import Logo from "./logo.svg?react";

function App() {
  return (
    <div>
      <Logo width={50} height={50} />
      <p>Hola desde Vite + SVGR</p>
    </div>
  );
}

👉 El sufijo ?react es clave, porque le indica al plugin que transforme el SVG en un componente de React en lugar de un simple archivo de imagen.

4. Tipado en TypeScript (opcional)

Si usas TypeScript, añade esta referencia para mejorar la inferencia de tipos:

/// <reference types="vite-plugin-svgr/client" />

5. Opciones avanzadas

El plugin permite pasar configuraciones de SVGR directamente:

svgr({
  svgrOptions: {
    icon: true, // convierte SVGs en íconos escalables
  },
  esbuildOptions: {
    // opciones para transformar JSX a JS
  },
  include: "**/*.svg?react", // patrón de archivos a incluir
});

✨ Beneficios de usar SVGR con Vite

  • Reutilización: Los SVGs se convierten en componentes reutilizables.
  • Personalización: Puedes aplicar props como width, height, fill, etc.
  • Optimización: SVGR optimiza automáticamente los SVGs para React.
  • Integración fluida: Funciona perfectamente con Vite y React sin configuración complicada.

En resumen: instala vite-plugin-svgr, configúralo en vite.config.js, y usa ?react al importar tus SVGs. Esto te permitirá tratarlos como componentes de React, con todas las ventajas de personalización y optimización que SVGR ofrece npm DEV Community.