Shared: Imports y boundaries
Use Absolute Paths (obligatorio)
Regla
- ✅ Importa usando alias
@/(rutas absolutas). - ❌ Evita
../../..fuera de la carpeta local.
Por qué
- Mejora refactors, reduce errores, acelera lectura en PR.
Ejemplos
// ✅ bien
import { Button } from '@/shared/ui/button';
import { ProductCard } from '@/entities/product';
import { AddToCartButton } from '@/features/cart/add-to-cart';
// ❌ mal
import { Button } from '../../../shared/ui/button';
Keep Things Close to Where They’re Used
Regla
-
✅ Trata de mantener todo cerca donde se usa dentro del feature correspondiente
-
❌ No mover a shared/ “por si acaso”.
Shared Domain
Regla clave sobre shared/domain
Qué va en shared/domain
Solo objetos base del dominio, tal como existen en la base de datos Ejemplo:
// shared/domain/post.ts
export type Post = {
id: string;
authorId: string;
content: string;
createdAt: string;
};
Estos objetos:
❌ no tienen lógica
❌ no tienen reglas
❌ no dependen de ningún feature
Son el lenguaje común del sistema.
Manipulación del dominio → siempre en el feature ❌ Incorrecto (shared demasiado específico)
// shared/domain/post.logic.ts ❌
export function canReplyToPost(user: User, post: Post) {
...
}
Esto depende del feature reply-to-post.
✅ Correcto (lógica en el feature)
// features/reply-to-post/domain/reply.logic.ts
export function canReplyToPost(user: User, post: Post) {
return user.id !== post.authorId;
}
Aquí:
User y Post vienen de shared/domain
la regla vive en el feature
el feature sigue siendo independiente y borrable
Regla mental final (para que no haya spaghetti)
Shared define “qué es algo”. El feature define “qué se puede hacer con eso”.
Si algo en shared:
empieza a tener reglas
cambia por un solo feature
“sabe” de flujos o pantallas
👉 está en el lugar equivocado y debe moverse al feature.
Que pasa cuando un feature es muy general y se usa en varios features o flujos
El ejemplo mas claro es uploadImage que es un flujo completo pero que puede ser usado distitno dentro de cada feature
Opción 1 (recomendada) convertirlo en un shared capability (shared/application + shared/infrastructure)
Si subir imágenes es una capacidad transversal (no una pantalla), trátalo como “infra + lógica reusable”, no como feature UI.
Estructura sugerida:
shared/
application/
mutations/
uploadImage.mutation.ts
infrastructure/
media/
media.api.ts
media.service.ts (solo si hay lógica)
media.transform.ts (si aplica)
Uso desde features:
features/create-post usa uploadImage.mutation.ts
features/reply-to-post usa uploadImage.mutation.ts
✅ Ventaja: reutilizas sin acoplar features ✅ Encaja con tu regla de capas (UI → Application → Infrastructure)
Opción 2
Mantenerlo como shared UI primitive + feature adapters
Si lo compartido es principalmente UI (un uploader), separa:
un componente genérico en shared (no sabe de posts/replies)
y un “adaptador” en cada feature (decide dónde guardar, qué validar, etc.)
Ejemplo estructura:
shared/ui/
media/
ImageUploader.tsx # genérico
features/
create-post/
ui/
components/
PostImageUploader.tsx # adapta a create-post
features/
reply-to-post/
ui/
components/
ReplyImageUploader.tsx # adapta a reply-to-post
✅ Shared = “motor genérico” ✅ Feature = “adaptación al flujo”
Opción 3
cuando es MUY grande → modules/ o capabilities/
Si “media” ya es un bloque grande (upload, crop, preview, limits, retries, provider fallback), puedes darle una carpeta propia que no es un feature de producto, sino una capability.
capabilities/
media/
ui/
application/
domain/
infrastructure/
Luego los features consumen capabilities/media/*.
✅ Útil cuando crece mucho ❗ Ojo: sigue aplicando “no importar entre features”; capabilities es otra categoría
Manage Dependencies Between Modules (avoid spaghetti)
En una arquitectura feature-driven, la promesa es:
Puedes borrar un feature completo y el resto del sistema sigue funcionando.
Si eso no se cumple, usualmente es por:
- imports cruzados entre features
- lógica compartida metida “a la fuerza” en un feature
sharedconvertido en un basurero (god folder)
Esta sección define reglas para evitarlo.
1) Regla base: Features no se importan entre sí
✅ Permitido
feature-apuede importar:shared/*domain/*infrastructure/*(oapplication/*según convención)- capas internas dentro de
feature-a
❌ Prohibido
feature-aimportandofeature-b/*
Por qué
- Eso crea dependencia directa entre features
- Si borras
feature-b, rompesfeature-a - Se vuelve imposible refactorizar sin miedo
Si necesitas algo de otro feature, eso “algo” no es de ese feature. Debe moverse a
sharedo adomain(según el caso).
2) Regla complementaria: Se respetan las reglas de importación entre capas
Además de “no imports entre features”, se deben cumplir las reglas de capas:
Flujo permitido (dependencias hacia abajo)
UI → Application → Domain → Infrastructure
En otras palabras
- UI puede importar Application/Domain (según tu convención), pero no Infrastructure directo si tu estándar lo evita.
- Application puede usar Domain + Infrastructure.
- Domain NO importa ni React, ni Application, ni Infrastructure.
- Infrastructure NO importa UI (React) y NO define reglas de negocio.
Si rompes estas reglas, el acoplamiento sube y aparece spaghetti.
3) “¿Dónde pongo esto?” — regla rápida para evitar imports cruzados
Cuando algo se usa en dos features, pregúntate:
A) ¿Esto es negocio (meaningful domain)?
Ejemplos:
User,Money,Permission,Plan,Subscription- reglas como
canEdit,hasExceededLimit
✅ Va a domain/*
B) ¿Esto es UI genérico reutilizable?
Ejemplos:
Button,Modal,EmptyState,SkeletonFormField,DataTablegenérico
✅ Va a shared/ui/*
C) ¿Esto es un helper técnico sin “significado de negocio”?
Ejemplos:
formatDate,cn(),debounce,mapKeyslogger,env,assert
✅ Va a shared/lib/* o shared/utils/*
D) ¿Esto depende de un flujo específico o de una pantalla?
Ejemplos:
useUsersFilters()mapUsersToRowsForThisTable()- “esta validación solo aplica en el wizard X”
✅ Debe quedarse dentro del feature
4) Cuándo algo debe ser parte de shared
shared existe para cosas transversales (cross-cutting) que:
- se usan en múltiples features, y
- no tienen un “hogar” más correcto (Domain o Infrastructure), y
- no amarran a un flujo específico
Ejemplos buenos de shared:
- UI primitives (shadcn wrappers, design system)
- helpers genéricos (string/date/arrays)
- infra base (queryClient, fetch wrappers si existieran)
- convenciones comunes (tipos utilitarios, constants globales)
Ejemplos malos para shared:
shared/users/*con cosas del “Users feature”shared/hooks/useEditUserModal.tsshared/components/UserCard.tsxsi solo un feature lo usa o si codifica reglas de ese feature
5) Señales de que algo en shared se volvió demasiado específico 🚨
Si una cosa en shared…
- tiene el nombre de un feature:
Users,Billing,Dashboard,Checkout - importa cosas de un feature
- asume un workflow (“step 2 del wizard”, “tab X”)
- tiene props o parámetros “raros” que solo un feature entiende
- se cambia cada vez que cambia un solo feature
👉 entonces no es shared: es código de feature que se “fugó”.
6) Qué hacer cuando algo en shared se vuelve específico (solución)
Tienes 3 opciones limpias:
Opción 1 — Moverlo de vuelta al feature (la más común)
Si solo un feature lo usa o ya depende del flujo del feature:
shared/*→features/<feature>/*
Opción 2 — Promoverlo a Domain (si en realidad es negocio)
Si representa un concepto del negocio usado en varios features:
shared/*→domain/<concept>/*
Opción 3 — Dividir: core genérico vs adaptador específico
Esto es lo más elegante cuando hay una parte realmente reusable.
Ejemplo:
shared/ui/DataTable(genérico)features/users/ui/UsersTable(adaptador específico que arma columnas/rows)
Regla:
- shared = “motor”
- feature = “adaptación”
7) Regla final para cumplir la promesa feature-driven
Un feature solo puede depender de shared + capas globales.
Nunca de otro feature.
Si necesitas reutilizar algo:
- primero intenta extraerlo a
domain(si es negocio) - si no, a
shared(si es genérico) - si es específico, se queda dentro del feature
Esto evita spaghetti y mantiene refactors seguros.