Cómo organizar tus carpetas y archivos en React para escalar tu aplicación

Gabriel Jiménez | Hace 6 días

Muchas veces una aplicación comienza con unos cuantos componentes y carpetas simples. Pero conforme el proyecto crece, comienzan a aparecer problemas: archivos difíciles de encontrar, lógica duplicada, componentes mezclando demasiadas responsabilidades y una estructura que nadie entiende realmente. El problema no siempre es React, es la forma en la que organizamos el proyecto.


Una mala organización de carpetas y archivos puede hacer que desarrollar nuevas funcionalidades sea más lento, que hacer refactorizaciones dé miedo y que escribir pruebas sea complicado.


En este artículo aprenderás cómo organizar tus carpetas y archivos en React de una forma más clara y escalable. Veremos errores comunes, estrategias para estructurar proyectos pequeños y grandes, y cómo una buena organización puede ayudarte a escribir componentes más fáciles de mantener, reutilizar y probar.


¿Por qué debería preocuparme por organizar mis carpetas?

Organizar nuestras carpetas no es una tarea al azar. Es una parte fundamental del desarrollo de software y algo que, como desarrolladores, deberíamos tomar en serio desde las primeras etapas del proyecto. 


Cuando una aplicación es pequeña, la organización de carpetas parece no importar demasiado. Todo es fácil de encontrar y mover archivos no representa un problema. Pero conforme el proyecto crece, comienzan a aparecer señales de alerta: archivos difíciles de ubicar, lógica mezclada en lugares incorrectos, componentes gigantes y una estructura que cada vez cuesta más entender.


Con el tiempo esto se convierte en “código espagueti” — solo dios y la IA saben como funciona. Este tipo de código vuelve lento, frustrante y riesgoso seguir agregando o modificando nuestros proyectos.


Ahora bien, organizar carpetas desde el inicio no significa diseñar la arquitectura perfecta desde el día uno. De hecho, intentar hacerlo suele llevar a sobreingeniería. Lo normal es comenzar con una estructura simple y permitir que el mismo proyecto vaya comunicando sus necesidades conforme evoluciona.


Una buena organización no busca verse “bonita”. Busca facilitar el desarrollo, el mantenimiento, los refactors y las pruebas a largo plazo.


Principales problemas al organizar mis carpetas

Estos son algunos de los problemas más comunes al momento de organizar nuestras carpetas y archivos:


No tener referencias sobre la importancia de organizar nuestras carpetas

Muchas veces nunca escuchamos sobre las ventajas de tener una estructura clara, por lo que terminamos organizando archivos únicamente “como dios nos dio a entender”.


Organizar por tipo

Por ejemplo, guardar todos los componentes de formularios dentro de una sola carpeta. Aunque al inicio parece buena idea, conforme el proyecto crece comenzamos a mezclar funcionalidades completamente diferentes en un mismo lugar.


Tener una organización demasiado estricta

Por ejemplo, imponer reglas como que cada carpeta debe tener obligatoriamente una subcarpeta llamada components. Este tipo de estructuras rígidas suelen agregar complejidad innecesaria y dificultan adaptar el proyecto a nuevas necesidades.


Aplicar sobreingeniería desde el inicio

Muchas veces creemos que nuestro proyecto crecerá rápidamente y comenzamos creando arquitecturas complejas desde el primer día. Sin embargo, la realidad es que muchos proyectos se mantienen relativamente simples o incluso terminan abandonándose antes de necesitar esa complejidad.


Organizar por funcionalidad es la clave

Construimos software para resolver problemas reales del día a día. Desde gestionar las citas de una clínica veterinaria hasta registrar inversiones o controlar ventas dentro de un negocio.

Y precisamente esos mismos problemas de negocio suelen darnos la mejor pista sobre cómo deberíamos organizar nuestras carpetas y archivos.


Por ejemplo, imaginemos que estamos desarrollando el flujo para registrar citas en línea dentro de una clínica veterinaria. Tenemos un formulario con campos como paciente, nombre del dueño, motivo de la visita y fecha de la cita. 


En lugar de colocar el formulario dentro de una carpeta genérica como forms, podríamos encapsular todo este flujo dentro de una carpeta llamada VisitasEnLinea. Dentro de ella viviría todo lo relacionado con esa funcionalidad: componentes, hooks, validaciones, servicios, pruebas y cualquier otra pieza necesaria para resolver ese problema.


De esta manera, comenzamos a organizar nuestro proyecto alrededor de funcionalidades y no únicamente por tipos de archivos. Esto tiene una ventaja enorme: cualquier persona que trabaje sobre el proyecto podrá entender mucho más rápido qué problema de negocio está resolviendo cada carpeta. En otras palabras, la estructura del proyecto comienza a comunicar intención y contexto, en lugar de solo almacenar archivos.


Ejemplo práctico: Cómo estructurar las carpetas y archivos de un proyecto

Para este ejemplo práctico, imaginemos el siguiente caso:


Una clínica veterinaria necesita automatizar el flujo de seguimiento de expedientes. Para resolver esta tarea, necesitamos una página para dar de alta un expediente, editarlo, listar los expedientes existentes y consultar el detalle de un paciente.


Organización de carpetas


Una posible estructura de carpetas podría ser la siguiente:


LiusVET
  src
    Expedientes
    ExpedientesForm
    ExpedientesEdit
    ExpedientesNew
    ExpedientesDetails

Donde:


  • Expedientes. Contiene el listado de expedientes.
  • ExpedientesForm. Contiene todo lo relacionado con el formulario de expedientes. Por ejemplo: campos, validaciones, mensajes de error y componentes reutilizables del formulario.
  • ExpedientesEdit. Encapsula lo relacionado con la edición del expediente, como la consulta del recurso a editar, la denormalización de campos y el envío de información al endpoint de actualización.
  • ExpedientesNew. Encapsula lo relacionado con el alta de un nuevo expediente, como la normalización del payload y el envío de información al endpoint de creación.
  • ExpedientesDetails. Gestiona cómo se obtiene y muestra la información de un expediente en particular.

La ventaja de esta estructura es que cada carpeta representa una funcionalidad concreta dentro del módulo de expedientes. No estamos agrupando archivos únicamente porque “son componentes” o “son formularios”, sino porque todos colaboran para resolver una parte específica del flujo.


Si mañana necesitamos agregar una nueva funcionalidad dentro del expediente, podríamos seguir la misma convención usando nombres como ExpedientesHistorial, ExpedientesVacunas o ExpedientesArchivos. Ahora, si esa nueva funcionalidad también tiene un flujo tipo CRUD, podríamos repetir la misma idea: una carpeta para el listado, otra para el formulario, otra para el alta, otra para la edición y otra para el detalle.


Lo importante no es copiar esta estructura al pie de la letra, sino entender la intención: organizar el proyecto alrededor de funcionalidades claras que ayuden a cualquier persona a ubicarse rápidamente dentro del código.


Organización de archivos

Al igual que la organización de carpetas, tener archivos con una intención clara y siguiendo un patrón definido hace mucho más sencillo escalar nuestras aplicaciones con el tiempo.


Cuando los archivos no tienen una responsabilidad clara, es común terminar con componentes gigantes, lógica mezclada y nombres difíciles de entender. En cambio, una estructura consistente permite identificar rápidamente qué hace cada archivo y dónde debería vivir una nueva funcionalidad.


A continuación, veamos algunos ejemplos prácticos de cómo podemos estructurar nuestros archivos de una manera más clara y mantenible.


Un archivo entry por carpeta

Cada carpeta debería tener un archivo principal que funcione como punto de entrada. Este archivo representa la funcionalidad completa de esa carpeta.


src
  Expedientes
    Expedientes.jsx
    ExpedientesTable.jsx
    getExpedientes.js

// Expedientes/Expedientes.jsx

import ExpedientesTable from "./ExpedientesTable";
import { getExpedientes } from "./getExpedientes";

export default function Expedientes() {
  return <ExpedientesTable />;
}

El archivo entry debe tener el mismo nombre de la carpeta

Esto ayuda a identificar rápidamente cuál es el archivo principal.


src
  ExpedientesNew
    ExpedientesNew.jsx
    ExpedientesForm.jsx
    createExpediente.js


// ExpedientesNew/ExpedientesNew.jsx

import ExpedientesForm from "./ExpedientesForm";
import { createExpediente } from "./createExpediente";

export default function ExpedientesNew() {
  return <ExpedientesForm onSubmit={createExpediente} />;
}

Todos los archivos deben vivir en el mismo nivel

Evita crear subcarpetas innecesarias dentro de cada funcionalidad. Si la carpeta todavía es pequeña, mantener todo al mismo nivel facilita encontrar los archivos.


src
  ExpedientesEdit
    ExpedientesEdit.jsx
    ExpedientesForm.jsx
    getExpediente.js
    updateExpediente.js
    normalizeExpedientePayload.js

Cualquier archivo que no sea entry debe expresar claramente su intención

El nombre del archivo debería decir qué hace, sin tener que abrirlo.


ExpedientesEdit
  ExpedientesEdit.jsx
  getExpediente.js
  updateExpediente.js
  normalizeExpedientePayload.js


// ExpedientesEdit/normalizeExpedientePayload.js

export function normalizeExpedientePayload(data) {
  return {
    patient_name: data.patientName,
    owner_name: data.ownerName,
    reason: data.reason,
  };
}

Los hooks deben seguir la convención useIntención

Los hooks personalizados deben comenzar con use y expresar claramente qué lógica encapsulan.


// hooks/useExpedienteForm.js

import { useState } from "react";

export function useExpedienteForm(initialValues) {
  const [data, setData] = useState(initialValues);

  function updateField(field, value) {
    setData((currentData) => ({
      ...currentData,
      [field]: value,
    }));
  }

  return {
    data,
    updateField,
  };
}

Los hooks deben vivir dentro de una carpeta hooks en la raíz del proyecto

Cuando un hook se comparte entre diferentes funcionalidades, puede vivir en una carpeta global.


src
  hooks
    useExpedienteForm.js
    useDebounce.js
    usePagination.js


// ExpedientesNew/ExpedientesNew.jsx

import { useExpedienteForm } from "../hooks/useExpedienteForm";

export default function ExpedientesNew() {
  const { data, updateField } = useExpedienteForm({
    patientName: "",
    ownerName: "",
    reason: "",
  });

  return null;
}

Los componentes reutilizables deben vivir en components

Si un componente se utiliza en diferentes partes del proyecto, no debería pertenecer a una funcionalidad específica.


src
  components
    Button.jsx
    TextInput.jsx
    Card.jsx


// components/TextInput.jsx

export default function TextInput({ label, value, onChange }) {
  return (
    <label>
      {label}
      <input value={value} onChange={onChange} />
    </label>
  );
}

Y luego puede utilizarse desde cualquier funcionalidad:


// ExpedientesForm.jsx

import TextInput from "../components/TextInput";

export default function ExpedientesForm() {
  return (
    <TextInput
      label="Nombre del paciente"
      value=""
      onChange={() => {}}
    />
  );
}

Los archivos de prueba deben vivir al mismo nivel del archivo que están probando

El código y sus pruebas viven cerca uno del otro, haciendo mucho más sencillo encontrar, mantener y actualizar las pruebas conforme el proyecto evoluciona.


ExpedientesEdit
  ExpedientesEdit.jsx
  ExpedientesEdit.test.jsx
  ExpedientesForm.jsx
  ExpedientesForm.test.jsx
  getExpediente.js
  getExpediente.test.js
  normalizeExpedientePayload.js
  normalizeExpedientePayload.test.js

Cómo una buena estructura facilita el testing en React

Tener una estructura fácil de leer y modificar permite encapsular responsabilidades de una manera mucho más clara. Y cuando las responsabilidades están bien separadas, escribir pruebas automatizadas se vuelve mucho más sencillo.

En otras palabras, una buena organización de carpetas y archivos no solo mejora la mantenibilidad del proyecto, también facilita probar cada pieza de forma aislada.

Veamos un ejemplo práctico de cómo una buena estructura puede facilitar el testing en React.

Supongamos que tenemos la siguiente estructura:


ExpedientesEdit
  ExpedientesEdit.jsx
  ExpedientesForm.jsx
  getExpediente.js
  updateExpediente.js
  normalizeExpedientePayload.js

Gracias a esta separación, podemos probar cada responsabilidad de forma independiente.

Por ejemplo, podemos probar la normalización del payload sin necesidad de renderizar componentes de React:


// normalizeExpedientePayload.test.js

import { normalizeExpedientePayload } from "./normalizeExpedientePayload";

describe("normalizeExpedientePayload", () => {
  it("normaliza correctamente el payload", () => {
    const payload = normalizeExpedientePayload({
      patientName: "Max",
      ownerName: "Gabriel",
      reason: "Vacunación",
    });

    expect(payload).toEqual({
      patient_name: "Max",
      owner_name: "Gabriel",
      reason: "Vacunación",
    });
  });
});

También podemos probar el componente del formulario de manera aislada:


// ExpedientesForm.test.jsx

import { render, screen } from "@testing-library/react";
import ExpedientesForm from "./ExpedientesForm";

describe("ExpedientesForm", () => {
  it("muestra el campo nombre del paciente", () => {
    render(<ExpedientesForm />);

    expect(
      screen.getByLabelText("Nombre del paciente")
    ).toBeInTheDocument();
  });
});

Y finalmente, podemos probar el flujo principal sin preocuparnos por la lógica interna de cada archivo:


// ExpedientesEdit.test.jsx

import { render, screen } from "@testing-library/react";
import ExpedientesEdit from "./ExpedientesEdit";

describe("ExpedientesEdit", () => {
  it("renderiza el formulario de edición", () => {
    render(<ExpedientesEdit />);

    expect(
      screen.getByText("Editar expediente")
    ).toBeInTheDocument();
  });
});

La ventaja de esta estructura es que cada archivo tiene una responsabilidad clara. Esto evita tener componentes gigantes con demasiada lógica mezclada y permite escribir pruebas más pequeñas, entendibles y fáciles de mantener.


Al final, una buena estructura no solo ayuda a encontrar archivos más rápido. También ayuda a crear software más fácil de probar, refactorizar y escalar.


Conclusión

Organizar correctamente nuestras carpetas y archivos permite crear aplicaciones más fáciles de mantener, escalar y probar. No necesitas una estructura perfecta desde el inicio. Lo importante es comenzar simple y permitir que el mismo proyecto vaya comunicando sus necesidades conforme crece.


Muchas veces, cuando una funcionalidad comienza a generar demasiado ruido, es una señal de que llegó el momento de encapsularla mejor mediante una estructura más clara.


Si quieres aprender cómo el testing puede ayudarte a crear aplicaciones más fáciles de escalar, mantener y refactorizar sin miedo a romper funcionalidades, te recomiendo visitar mi hub de Testing en React.


Hub Testing en React.

Más de esta serie

Cómo consumir APIs en React sin ensuciar tus componentes

Cómo consumir APIs en React sin ensuciar tus componentes

Aprende cómo transformar datos entre React y tu backend usando adapters y mappers para crear componentes más fáciles de mantener y escalar.

¿Cuándo usar hooks personalizados en React (y cuándo no)?

¿Cuándo usar hooks personalizados en React (y cuándo no)?

Aprende cuándo crear hooks personalizados en React para reutilizar lógica, simplificar componentes y mejorar el testing.

Por qué tu componente en React es difícil de probar (y cómo solucionarlo)

Por qué tu componente en React es difícil de probar (y cómo solucionarlo)

Aprende por qué algunos componentes en React son difíciles de probar y cómo organizarlos para crear código más escalable y mantenible.

Señales de un componente difícil de mantener en React

Señales de un componente difícil de mantener en React

Aprende a detectar componentes difíciles de mantener en React antes de que se conviertan en deuda técnica y errores costosos.