jueves, 1 de enero de 2026

Código limpio para crear diferentes objetos JSX dependiendo de un discriminante

 ¿Cómo puedo organizar mejor un código que se ocupa de la creación de muchos objetos basado en la concatenación de varios operadores ?: (ternary operator) evaluando un discriminante y creando uno u otro objeto según el discriminante?

Muy buena pregunta, Gabriel 👌. Cuando el código empieza a tener muchos operadores ternarios concatenados para decidir qué objeto crear, la legibilidad y el mantenimiento se vuelven un problema. Hay varias formas más limpias y escalables de organizarlo:

🔹 1. Usar un mapa de discriminantes a constructores

En vez de encadenar ternarios, podés definir un objeto/diccionario que mapea el discriminante a la función creadora:

ts
type Discriminante = "A" | "B" | "C";

const factories: Record<Discriminante, () => Objeto> = {
  A: () => new ObjetoA(),
  B: () => new ObjetoB(),
  C: () => new ObjetoC(),
};

function crearObjeto(tipo: Discriminante): Objeto {
  return factories[tipo]();
}

👉 Ventaja: agregar un nuevo tipo es tan simple como añadir una entrada al mapa.

🔹 2. Usar un switch/case

Más claro que varios ternarios, especialmente si hay lógica adicional:

ts
function crearObjeto(tipo: string): Objeto {
  switch (tipo) {
    case "A":
      return new ObjetoA();
    case "B":
      return new ObjetoB();
    case "C":
      return new ObjetoC();
    default:
      throw new Error(`Tipo desconocido: ${tipo}`);
  }
}

👉 Ventaja: legible, fácil de seguir, pero puede crecer mucho si hay muchos casos.

🔹 3. Patrón Factory

Si la creación de objetos es más compleja, podés encapsularla en una clase o módulo “Factory”:

ts
class ObjetoFactory {
  static crear(tipo: string): Objeto {
    if (tipo === "A") return new ObjetoA();
    if (tipo === "B") return new ObjetoB();
    if (tipo === "C") return new ObjetoC();
    throw new Error(`Tipo desconocido: ${tipo}`);
  }
}

👉 Ventaja: centralizás la lógica de creación y podés extenderla con configuraciones, parámetros, etc.

🔹 4. Funciones puras con discriminante

Si los objetos comparten estructura, podés usar funciones que devuelvan configuraciones distintas:

ts
function crearObjeto(tipo: string): Objeto {
  const base = { activo: true };

  if (tipo === "A") return { ...base, nombre: "Objeto A" };
  if (tipo === "B") return { ...base, nombre: "Objeto B" };
  return { ...base, nombre: "Default" };
}

👉 Ventaja: menos clases, más declarativo.

✨ Conclusión

  • Si son pocos casosswitch/case es suficiente.

  • Si son muchos y repetitivos → mejor un mapa de funciones.

  • Si la lógica es compleja → usá un Factory Pattern.

Esto evita el “spaghetti” de ternarios y hace que tu código sea más mantenible y escalable.

¿Querés que te arme un ejemplo con TypeScript + discriminated unions para que el compilador te ayude a asegurar que cada tipo de objeto está cubierto sin olvidos?

El problema es que lo que quiero crear son componentes expresados por JSX. Es decir, los objetos se instancian declarativamente. No uso el operador new por ningún lado.

¡Perfecto, Gabriel! 👌 Ahora entiendo mejor tu caso: no estás creando instancias con new, sino que tu “creación de objetos” en realidad es renderizado de componentes JSX. En ese contexto, concatenar muchos operadores ternarios para decidir qué componente mostrar puede volverse ilegible. Te muestro formas más limpias de organizarlo:

🔹 1. Usar un mapa de discriminantes a componentes

En vez de encadenar ternarios, definís un diccionario que mapea el discriminante al componente JSX:

jsx
const componentMap = {
  A: <ComponenteA />,
  B: <ComponenteB />,
  C: <ComponenteC />,
};

function Renderizador({ tipo }) {
  return componentMap[tipo] ?? <ComponenteDefault />;
}

👉 Ventaja: agregar un nuevo caso es tan simple como añadir una entrada al mapa.

🔹 2. Usar un switch/case dentro de una función

Más claro que varios ternarios, especialmente si cada componente necesita props distintos:

jsx
function renderComponent(tipo) {
  switch (tipo) {
    case "A":
      return <ComponenteA prop1="valor" />;
    case "B":
      return <ComponenteB />;
    case "C":
      return <ComponenteC />;
    default:
      return <ComponenteDefault />;
  }
}

function App({ tipo }) {
  return <div>{renderComponent(tipo)}</div>;
}

🔹 3. Patrón Factory declarativo

Podés encapsular la lógica en una función “factory” que devuelva JSX:

jsx
function ComponentFactory({ tipo }) {
  if (tipo === "A") return <ComponenteA />;
  if (tipo === "B") return <ComponenteB />;
  if (tipo === "C") return <ComponenteC />;
  return <ComponenteDefault />;
}

👉 Esto centraliza la lógica y mantiene tu JSX principal limpio.

🔹 4. Discriminated unions con TypeScript

Si usás TypeScript, podés aprovechar los union types para que el compilador te obligue a cubrir todos los casos:

tsx
type Tipo = "A" | "B" | "C";

function ComponentFactory({ tipo }: { tipo: Tipo }) {
  switch (tipo) {
    case "A": return <ComponenteA />;
    case "B": return <ComponenteB />;
    case "C": return <ComponenteC />;
  }
}

👉 Si olvidás un caso, TypeScript te avisa.

✨ Conclusión

  • Muchos ternarios → ilegible.

  • Mejor usar un mapa de componentes o un switch/case.

  • Si querés escalabilidad, encapsulá la lógica en un factory declarativo.

  • Con TypeScript, los union types te ayudan a no olvidar casos.

¿Querés que te arme un ejemplo completo con mapa de componentes + props dinámicos (ej. cada componente recibe props distintos según el discriminante) para que veas cómo se organiza en un proyecto real?