Skip to content

💻 Desarrollo Día a Día

Objetivo: Dominar los workflows diarios para ser 100% productivo desarrollando microfrontends con nuestro stack.


Terminal window
# 1. Actualizar workspace
cd proyecto-vue
git pull origin main
pnpm install
# 2. Build packages si hay cambios
pnpm -r build
# 3. Iniciar desarrollo
pnpm app-{mi-proyecto} dev

🛠️ Modificar Código

  1. Editar componente/store/composable
  2. Hot reload automático en navegador
  3. Verificar en DevTools
  4. Continuar iterando

🧪 Verificar Cambios

  1. Lint automático en VS Code 2. Type checking en tiempo real 3. Console limpia sin errores 4. Pinia DevTools para estado

📦 Preparar Commit

  1. pnpm app-{proyecto} lint - Fix automático
  2. pnpm app-{proyecto} build - Verificar build
  3. git add y git commit
  4. Push con confianza
Terminal window
# Generar desde proyecto-vue (genera ejemplo funcional)
cd proyecto-vue && git pull
npx plop # Seleccionar tipo según Decision Tree
# Copiar y configurar en workspace real
Copy-Item -Recurse @apps/mi-nuevo-proyecto ../mi-workspace-real/@apps/
cd ../mi-workspace-real
# Agregar script: "app-{nombre}": "pnpm --filter {nombre}"
pnpm install && pnpm -r build
# Ejecutar ejemplo generado
pnpm app-{nombre} dev
# → Verificar que funciona, luego adaptar a tu caso específico

Post-generación workflow típico:

Terminal window
# 1. Entender código generado
code @apps/{nombre}/src/
# 2. Adaptar global.ts a tu dominio
# 3. Modificar store para tu API
# 4. Adaptar componentes a tu UI
# 5. Desarrollo iterativo normal

El template genera un store de ejemplo que puedes adaptar:

// stores/productStore.ts (generado por template)
export const useProductStore = defineStore("products", {
state: (): StoreState => ({
// ✅ Configuración inyectada (mantener)
data: inject("configWindow") as Params | null,
configEnv: inject("configEnv") as ConfigEnv,
// ✅ Loading states (mantener pattern)
isLoading: false,
// 🔄 ADAPTAR: Cambiar products por tu dominio
flights: [], // Era: products: []
selectedFlight: null, // Era: selectedProduct: null
error: null,
}),
getters: {
// ✅ Mantener getters de configuración
currency: (state): string => state.data?.config.currency ?? "USD",
// 🔄 ADAPTAR: Cambiar lógica específica
availableFlights: (state): Flight[] => state.flights.filter((flight) => flight.available),
},
actions: {
// ✅ Mantener pattern de async actions
async fetchFlights(): Promise<void> {
// Era: fetchProducts
this.isLoading = true;
this.error = null;
try {
// 🔄 ADAPTAR: Tu API específica
const response = await apiClient.get(`/flights/${this.searchId}`);
if (response) {
this.flights = response; // Era: this.products
}
} catch (error) {
this.error = error instanceof Error ? error.message : "Unknown error";
} finally {
this.isLoading = false;
}
},
},
});
composables/useApiClient.ts
import { ref } from "vue";
import type { ApiResponse } from "@pnpmworkspace/types";
export function useApiClient(baseUrl: string) {
const isLoading = ref(false);
const error = ref<string | null>(null);
async function get<T>(endpoint: string): Promise<T | null> {
isLoading.value = true;
error.value = null;
try {
const response = await fetch(`${baseUrl}${endpoint}`);
const data: ApiResponse<T> = await response.json();
if (data.success) {
return data.data || null;
} else {
error.value = data.error || "API Error";
return null;
}
} catch (err) {
error.value = err instanceof Error ? err.message : "Network Error";
return null;
} finally {
isLoading.value = false;
}
}
return {
isLoading: readonly(isLoading),
error: readonly(error),
get,
};
}
<script setup lang="ts">
import { reactive } from "vue";
import { useValidation } from "@/composables/useValidation";
interface FormData {
name: string;
email: string;
age: number | null;
}
const formData = reactive<FormData>({
name: "",
email: "",
age: null,
});
const { errors, validate, isValid } = useValidation(formData, {
name: { required: true, minLength: 2 },
email: { required: true, email: true },
age: { required: true, min: 18 },
});
async function handleSubmit(): Promise<void> {
if (!validate()) return;
// Submit logic
await submitForm(formData);
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<div class="field">
<label for="name">Nombre</label>
<input
id="name"
v-model="formData.name"
:class="{ error: errors.name }"
type="text"
/>
<span
v-if="errors.name"
class="error-text"
>{{ errors.name }}</span
>
</div>
<button
type="submit"
:disabled="!isValid"
>
{{ $t("common.submit") }}
</button>
</form>
</template>

stores/userStore.ts
import { defineStore } from "pinia";
import { inject } from "vue";
import type { User, ConfigEnv } from "@pnpmworkspace/types";
interface UserState {
// Configuración
configEnv: ConfigEnv;
// Estados de loading específicos
isLoading: boolean;
isUpdating: boolean;
// Datos principales
currentUser: User | null;
users: User[];
// Errores específicos
fetchError: string | null;
updateError: string | null;
}
export const useUserStore = defineStore("user", {
state: (): UserState => ({
configEnv: inject("configEnv") as ConfigEnv,
isLoading: false,
isUpdating: false,
currentUser: null,
users: [],
fetchError: null,
updateError: null,
}),
getters: {
// Estados agregados
hasAnyError: (state): boolean => Boolean(state.fetchError || state.updateError),
isAnyLoading: (state): boolean => state.isLoading || state.isUpdating,
// Transformaciones de negocio
activeUsers: (state): User[] => state.users.filter((user) => user.isActive),
userById:
(state) =>
(id: string): User | undefined =>
state.users.find((user) => user.id === id),
},
actions: {
// Limpieza de errores
clearErrors(): void {
this.fetchError = null;
this.updateError = null;
},
// Fetching con error handling
async fetchUsers(): Promise<void> {
this.isLoading = true;
this.fetchError = null;
try {
const response = await apiClient.get<User[]>("/users");
if (response) {
this.users = response;
}
} catch (error) {
this.fetchError = error instanceof Error ? error.message : "Unknown error";
} finally {
this.isLoading = false;
}
},
// Updating con optimistic updates
async updateUser(userId: string, updates: Partial<User>): Promise<boolean> {
this.isUpdating = true;
this.updateError = null;
// Optimistic update
const originalUser = this.userById(userId);
if (originalUser) {
Object.assign(originalUser, updates);
}
try {
const response = await apiClient.put<User>(`/users/${userId}`, updates);
if (response) {
// Confirm with server response
const index = this.users.findIndex((u) => u.id === userId);
if (index !== -1) {
this.users[index] = response;
}
return true;
}
return false;
} catch (error) {
// Revert optimistic update
if (originalUser) {
const index = this.users.findIndex((u) => u.id === userId);
if (index !== -1) {
this.users[index] = originalUser;
}
}
this.updateError = error instanceof Error ? error.message : "Update failed";
return false;
} finally {
this.isUpdating = false;
}
},
// Inicialización
async initialize(): Promise<void> {
this.clearErrors();
await this.fetchUsers();
},
},
});
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useUserStore } from "@/stores/userStore";
const userStore = useUserStore();
// ✅ Correcto: storeToRefs mantiene reactividad
const { users, isLoading, hasAnyError } = storeToRefs(userStore);
// ✅ Correcto: actions se pueden desestructurar directamente
const { fetchUsers, updateUser } = userStore;
// ❌ Incorrecto: perdería reactividad
// const { users, isLoading } = userStore;
</script>
<template>
<div>
<button
@click="fetchUsers()"
:disabled="isLoading"
>
{{ $t("common.refresh") }}
</button>
<div
v-if="hasAnyError"
class="error"
>
{{ $t("errors.general") }}
</div>
<UserList
:users="users"
:loading="isLoading"
@update="updateUser"
/>
</div>
</template>

📁 public/locales/
├── 📁 en-US/
│ └── 📄 translation.json
├── 📁 es-MX/
│ └── 📄 translation.json
└── 📁 es-CO/
└── 📄 translation.json
public/locales/en-US/translation.json
{
"common": {
"loading": "Loading...",
"error": "Error",
"retry": "Try Again",
"submit": "Submit",
"cancel": "Cancel"
},
"products": {
"title": "Products",
"addToCart": "Add to Cart",
"outOfStock": "Out of Stock",
"price": "Price: {{amount}} {{currency}}"
},
"errors": {
"network": "Network error. Please check your connection.",
"validation": {
"required": "This field is required",
"email": "Please enter a valid email",
"minLength": "Minimum {{min}} characters required"
}
}
}
<template>
<div>
<!-- Traducción simple -->
<h1>{{ $t("products.title") }}</h1>
<!-- Con interpolación -->
<p>{{ $t("products.price", { amount: 29.99, currency: "USD" }) }}</p>
<!-- Pluralización -->
<span>{{ $t("common.items", { count: itemCount }) }}</span>
<!-- Conditional -->
<button v-if="inStock">
{{ $t("products.addToCart") }}
</button>
<span v-else>{{ $t("products.outOfStock") }}</span>
</div>
</template>
composables/useLanguage.ts
import { useI18n } from "i18next-vue";
import { ref } from "vue";
export function useLanguage() {
const { i18n } = useI18n();
const currentLanguage = ref(i18n.language);
async function changeLanguage(lang: string): Promise<void> {
await i18n.changeLanguage(lang);
currentLanguage.value = lang;
// Guardar preferencia
localStorage.setItem("user-language", lang);
}
return {
currentLanguage: readonly(currentLanguage),
changeLanguage,
};
}

🔧 Vue DevTools

Instalación: Extension de Chrome/Firefox

Uso diario:

  • Inspeccionar componentes y props
  • Ver estado reactivo en tiempo real
  • Timeline de eventos

🗃️ Pinia DevTools

Incluido en Vue DevTools Uso diario: - Estado de todos los stores - Timeline de mutations

  • Time travel debugging

⚡ Vite DevTools

Automático en desarrollo

Uso diario:

  • Hot Module Replacement
  • Source maps
  • Network timing

❌ “Cannot find module ‘@pnpmworkspace/…’”

Terminal window
# 1. Verificar que packages están built
pnpm -r build
# 2. Restart TypeScript server en VS Code
# Ctrl+Shift+P → "TypeScript: Restart TS Server"
# 3. Limpiar cache si persiste
Remove-Item node_modules/.vite -Recurse -Force

❌ “Type errors en tiempo real”

Terminal window
# Verificar que usa workspace TypeScript
# VS Code: Ctrl+Shift+P → "TypeScript: Select Version" → "Use Workspace Version"
// Debug helpers para Pinia stores
export const useDebugStore = defineStore("debug", {
actions: {
logAllStores() {
// En development only
if (import.meta.env.DEV) {
console.group("🗃️ Pinia Stores State");
// Iterar todos los stores y loggear estado
console.groupEnd();
}
},
logApiCalls() {
// Interceptar y loggear llamadas de API
if (import.meta.env.DEV) {
console.log("📡 API Call:", arguments);
}
},
},
});

<script setup lang="ts">
import { defineAsyncComponent } from "vue";
// ✅ Lazy load componentes pesados
const HeavyChart = defineAsyncComponent(() => import("@/components/HeavyChart.vue"));
const DataTable = defineAsyncComponent(() => import("@/components/DataTable.vue"));
const showChart = ref(false);
</script>
<template>
<div>
<button @click="showChart = !showChart">{{ showChart ? "Hide" : "Show" }} Chart</button>
<!-- Solo carga cuando se necesita -->
<Suspense v-if="showChart">
<HeavyChart :data="chartData" />
<template #fallback>
<div class="loading">Loading chart...</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { computed, shallowRef } from "vue";
// ✅ Para arrays grandes, usar shallowRef
const largeDataset = shallowRef<Item[]>([]);
// ✅ Memoization manual para cálculos costosos
const expensiveComputation = computed(() => {
console.log("🔄 Recalculating expensive computation");
return largeDataset.value
.filter((item) => item.isActive)
.sort((a, b) => b.priority - a.priority)
.slice(0, 100); // Solo top 100
});
// ✅ Computed dependientes específicos
const filteredCount = computed(() => expensiveComputation.value.length);
</script>
Terminal window
# Analizar tamaño de build
pnpm app-{proyecto} build
# Ver breakdown de chunks
Get-ChildItem dist/ | Where-Object { $_.Extension -match '\.(js|css)$' }
# Para análisis detallado (opcional)
# npm install -g bundlemon
# bundlemon

  • pnpm app-{proyecto} lint sin errores
  • pnpm app-{proyecto} build exitoso
  • No errores TypeScript en VS Code
  • Hot reload funcionando
  • Console del navegador limpia
  • Vue DevTools sin warnings
  • Build de producción exitoso
  • Todos los packages actualizados (pnpm -r build)
  • No dependencias nuevas sin aprobar
  • Código sigue estándares de naming
  • Traducciones añadidas si es necesario
  • Vue DevTools instalado y funcionando
  • TypeScript usando workspace version
  • Pnpm cache limpio si hay problemas raros
  • Variables de entorno correctas
  • Packages del workspace built

Ahora que dominas el desarrollo día a día:

  1. 🏗️ Arquitectura Avanzada - Patterns y comunicación entre microfrontends
  2. 📚 Referencias - Configuraciones para copiar y pegar
  3. 🎯 Templates - Acelerar creación de nuevos proyectos