Vue 4 + Nuxt — Arquitectura por capas
Una responsabilidad por capa. Sin excepciones.
Mapa de capas
composables/ → lógica reactiva reutilizable, sin efecto de red
stores/ → estado global (Pinia)
services/ → acceso a red y efectos externos
components/ → UI pura y declarativa
pages/ → nivel de ruta, orquesta las capas
layouts/ → estructura de página persistente
utils/ → funciones puras sin estado
types/ → definiciones TypeScript compartidas
composables/ — lógica reactiva
- Funciones con prefijo
use: useCart, useValidation, usePagination.
- Encapsulan
ref, computed, watch y lógica derivada.
- Sin llamadas de red directas — si necesitan datos externos, reciben un service como parámetro o delegan a la store.
- Reutilizables entre páginas y componentes.
// composables/useCounter.ts
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const reset = () => (count.value = initial)
return { count: readonly(count), increment, reset }
}
stores/ — estado global (Pinia)
- Una store por dominio:
useAuthStore, useCartStore, useNotificationsStore.
- Sin lógica de negocio gorda — delegá en composables o services.
- Las actions llaman a
services/ para efectos externos.
- El estado es la única fuente de verdad para datos globales.
// stores/auth.ts
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
const isAuthenticated = computed(() => user.value !== null)
async function login(credentials: LoginCredentials) {
user.value = await authService.login(credentials)
}
return { user: readonly(user), isAuthenticated, login }
})
services/ — acceso a red
- Un archivo por dominio:
userService.ts, productService.ts.
- Toda llamada a
$fetch, useFetch o librerías externas vive aquí.
- Tipado estricto de request y response.
- Sin reactividad — devuelven datos planos o Promises.
// services/userService.ts
export const userService = {
async getProfile(id: string): Promise<UserProfile> {
return $fetch(`/api/users/${id}`)
},
async updateProfile(id: string, data: UpdateProfileInput): Promise<UserProfile> {
return $fetch(`/api/users/${id}`, { method: 'PATCH', body: data })
},
}
components/ — UI pura
<script setup> (con lang="ts" según el proyecto).
- Reciben datos por props, comunican eventos por emits.
- Sin acceso directo a stores desde componentes hoja — recibí los datos como prop.
- Excepción: componentes de layout o "container" que orquestan stores explícitamente.
- Nombrados en PascalCase:
ProductCard.vue, BaseButton.vue.
components/
├── base/ → átomos del design system (BaseButton, BaseInput)
├── common/ → moléculas compartidas (SearchBar, Pagination)
└── [feature]/ → organismos específicos de feature (ProductCard, CheckoutSummary)
pages/ — nivel de ruta
- Archivos mapeados a rutas por Nuxt (
pages/products/[id].vue).
- Orquestan composables, stores y llaman a services vía stores.
- Thin: la mayor parte de la lógica vive en composables y stores.
- Sin lógica de negocio directamente en el template.
layouts/ — estructura de página
layouts/default.vue, layouts/auth.vue, etc.
- Solo estructura visual persistente (header, footer, sidebar).
- Sin lógica de dominio.
utils/ — funciones puras
- Sin estado, sin reactividad, sin efectos.
- Testeables de forma aislada.
formatDate, slugify, clamp, groupBy.
Regla de oro — ¿dónde va esto?
| ¿Qué hace el código? |
Archivo |
| Lógica reactiva reutilizable |
composables/ |
| Estado global compartido |
stores/ |
| Llamada a red / API externa |
services/ |
| UI declarativa |
components/ |
| Orquestación nivel ruta |
pages/ |
| Función pura sin estado |
utils/ |
| Tipos compartidos |
types/ |
Anti-patrones (rechazar en revisión)
$fetch directamente en un <script setup> de un componente hoja.
- Store con lógica de negocio gorda en una action de 100 líneas.
- Composable que accede a otra store directamente (crea acoplamiento implícito).
- Página con 300 líneas de template — extraer a componentes.
utils/ con funciones con efectos secundarios o estado.
- Importar una store desde un componente hoja que debería recibir la data como prop.
Source: maigueldev/claude-orchestrator — distributed by TomeVault.
1---2name: vue-nuxt-architecture3description: Arquitectura por capas para Vue 4 + Nuxt — composables/stores/services/components/pages, reglas de responsabilidad por capa y anti-patrones a rechazar. Invocar al iniciar cualquier tarea frontend. Use when this capability is needed.4---56# Vue 4 + Nuxt — Arquitectura por capas78Una responsabilidad por capa. Sin excepciones.910## Mapa de capas1112```13composables/ → lógica reactiva reutilizable, sin efecto de red14stores/ → estado global (Pinia)15services/ → acceso a red y efectos externos16components/ → UI pura y declarativa17pages/ → nivel de ruta, orquesta las capas18layouts/ → estructura de página persistente19utils/ → funciones puras sin estado20types/ → definiciones TypeScript compartidas21```2223## `composables/` — lógica reactiva2425- Funciones con prefijo `use`: `useCart`, `useValidation`, `usePagination`.26- Encapsulan `ref`, `computed`, `watch` y lógica derivada.27- **Sin llamadas de red directas** — si necesitan datos externos, reciben un service como parámetro o delegan a la store.28- Reutilizables entre páginas y componentes.2930```ts31// composables/useCounter.ts32export function useCounter(initial = 0) {33 const count = ref(initial)34 const increment = () => count.value++35 const reset = () => (count.value = initial)36 return { count: readonly(count), increment, reset }37}38```3940## `stores/` — estado global (Pinia)4142- Una store por dominio: `useAuthStore`, `useCartStore`, `useNotificationsStore`.43- **Sin lógica de negocio gorda** — delegá en composables o services.44- Las actions llaman a `services/` para efectos externos.45- El estado es la única fuente de verdad para datos globales.4647```ts48// stores/auth.ts49export const useAuthStore = defineStore('auth', () => {50 const user = ref<User | null>(null)51 const isAuthenticated = computed(() => user.value !== null)5253 async function login(credentials: LoginCredentials) {54 user.value = await authService.login(credentials)55 }5657 return { user: readonly(user), isAuthenticated, login }58})59```6061## `services/` — acceso a red6263- Un archivo por dominio: `userService.ts`, `productService.ts`.64- Toda llamada a `$fetch`, `useFetch` o librerías externas vive aquí.65- Tipado estricto de request y response.66- Sin reactividad — devuelven datos planos o Promises.6768```ts69// services/userService.ts70export const userService = {71 async getProfile(id: string): Promise<UserProfile> {72 return $fetch(`/api/users/${id}`)73 },74 async updateProfile(id: string, data: UpdateProfileInput): Promise<UserProfile> {75 return $fetch(`/api/users/${id}`, { method: 'PATCH', body: data })76 },77}78```7980## `components/` — UI pura8182- `<script setup>` (con `lang="ts"` según el proyecto).83- Reciben datos por props, comunican eventos por emits.84- **Sin acceso directo a stores** desde componentes hoja — recibí los datos como prop.85- Excepción: componentes de layout o "container" que orquestan stores explícitamente.86- Nombrados en PascalCase: `ProductCard.vue`, `BaseButton.vue`.8788```89components/90├── base/ → átomos del design system (BaseButton, BaseInput)91├── common/ → moléculas compartidas (SearchBar, Pagination)92└── [feature]/ → organismos específicos de feature (ProductCard, CheckoutSummary)93```9495## `pages/` — nivel de ruta9697- Archivos mapeados a rutas por Nuxt (`pages/products/[id].vue`).98- Orquestan composables, stores y llaman a services vía stores.99- **Thin**: la mayor parte de la lógica vive en composables y stores.100- Sin lógica de negocio directamente en el template.101102## `layouts/` — estructura de página103104- `layouts/default.vue`, `layouts/auth.vue`, etc.105- Solo estructura visual persistente (header, footer, sidebar).106- Sin lógica de dominio.107108## `utils/` — funciones puras109110- Sin estado, sin reactividad, sin efectos.111- Testeables de forma aislada.112- `formatDate`, `slugify`, `clamp`, `groupBy`.113114## Regla de oro — ¿dónde va esto?115116| ¿Qué hace el código? | Archivo |117|---|---|118| Lógica reactiva reutilizable | `composables/` |119| Estado global compartido | `stores/` |120| Llamada a red / API externa | `services/` |121| UI declarativa | `components/` |122| Orquestación nivel ruta | `pages/` |123| Función pura sin estado | `utils/` |124| Tipos compartidos | `types/` |125126## Anti-patrones (rechazar en revisión)127128- `$fetch` directamente en un `<script setup>` de un componente hoja.129- Store con lógica de negocio gorda en una action de 100 líneas.130- Composable que accede a otra store directamente (crea acoplamiento implícito).131- Página con 300 líneas de template — extraer a componentes.132- `utils/` con funciones con efectos secundarios o estado.133- Importar una store desde un componente hoja que debería recibir la data como prop.134135---136> Source: [maigueldev/claude-orchestrator](https://github.com/maigueldev/claude-orchestrator) — distributed by [TomeVault](https://tomevault.io).137<!-- tomevault:4.0:skill_md:2026-04-26 -->