Lo que NUNCA debes hacer
Este documento existe porque estos errores se han cometido antes. No los repitas.
Errores que rompen el commit (pre-commit hooks)
Section titled “Errores que rompen el commit (pre-commit hooks)”Estos impiden que hagas git commit. El hook los detecta y aborta:
// ❌ var — usa const o letvar items = []
// ❌ console.log — elimínaloconsole.log('debug:', data)
// ❌ Importar de vue — son auto-imports en Nuxtimport { ref, computed, watch } from 'vue'
// ❌ any — tipa correctamentefunction getData(): any { ... }const result: any = await fetch(...)Errores que rompen la aplicación
Section titled “Errores que rompen la aplicación”Mutar props
Section titled “Mutar props”<!-- ❌ NUNCA — Vue 3 props son readonly --><script setup lang="ts">const props = defineProps<{ count: number }>()props.count++ // ← TypeError en runtime</script>
<!-- ✅ Emite evento al padre --><script setup lang="ts">const props = defineProps<{ count: number }>()const emit = defineEmits<{ update: [value: number] }>()emit('update', props.count + 1)</script>Llamar servicios directamente (sin useServices)
Section titled “Llamar servicios directamente (sin useServices)”// ❌ NUNCA — rompe el fallback i18n y el multi-verticalimport { coursesService } from '~/services/inspiria/courses'const data = await coursesService.getCourses('es')
// ✅ SIEMPRE usa useServicesconst { data } = useServices('getCourses')Hardcodear la URL de la API
Section titled “Hardcodear la URL de la API”// ❌ NUNCA — la URL viene de la configuración de la verticalconst data = await $fetch('https://api.inspiriadental.com/api/courses')
// ✅ El httpClient resuelve la URL automáticamenteconst data = await httpClient.get({ resource: 'courses', params: '...' })Usar $route y $router directamente
Section titled “Usar $route y $router directamente”// ❌ No funciona en <script setup> y no es testeablethis.$router.push('/courses')
// ✅ Composablesconst router = useRouter()const route = useRoute()router.push('/courses')Errores de estilo que generan deuda técnica
Section titled “Errores de estilo que generan deuda técnica”Colores hardcodeados
Section titled “Colores hardcodeados”<!-- ❌ Si la vertical cambia de color, esto no se actualiza --><div class="bg-[#00ffa8]">...</div><div style="color: #333">...</div>
<!-- ✅ CSS variables via Tailwind --><div class="bg-primary">...</div><div class="text-graphite">...</div>SVGs inline en templates
Section titled “SVGs inline en templates”<!-- ❌ 40 líneas de SVG en medio del template --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M12 2C6.48..."/></svg>
<!-- ✅ Sistema de iconos --><Icon name="heroicons:academic-cap" class="size-6" />Desktop-first responsive
Section titled “Desktop-first responsive”<!-- ❌ Desktop-first (Tailwind es mobile-first) --><div class="grid grid-cols-3 sm:grid-cols-1">
<!-- ✅ Mobile-first --><div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">Errores de arquitectura
Section titled “Errores de arquitectura”Lógica de negocio en componentes
Section titled “Lógica de negocio en componentes”<!-- ❌ El componente hace demasiado — no es reutilizable ni testeable --><script setup lang="ts">const data = ref([])const isLoading = ref(false)
async function fetchAndProcess() { isLoading.value = true const raw = await httpClient.get({ resource: 'courses', params: '...' }) data.value = raw.filter(c => c.active).map(c => ({ ...c, price: c.fullPrice.price * (1 - c.fullPrice.discountPercentage / 100) })) isLoading.value = false}</script>
<!-- ✅ Extrae a composable o servicio --><script setup lang="ts">const { data, isPending } = useServices('getCourses')</script>Estado global en componentes (sin store)
Section titled “Estado global en componentes (sin store)”// ❌ Provee/inyecta estado global ad-hocconst globalCart = ref([]) // ← en un componente randomprovide('cart', globalCart)
// ✅ Usa Pinia store// stores/cart.tsexport const useCartStore = defineStore('cart', () => { const items = ref([]) return { items }})Composables con efectos secundarios no controlados
Section titled “Composables con efectos secundarios no controlados”// ❌ El composable modifica el DOM o hace redirects inesperadosexport function useAuth() { onMounted(() => { if (!token.value) { navigateTo('/login') // ← efecto secundario sorpresivo } })}
// ✅ Retorna estado, deja que el consumidor decidaexport function useAuth() { const isAuthenticated = computed(() => !!token.value) return { isAuthenticated } // ← el componente decide qué hacer}Errores de Git
Section titled “Errores de Git”Commit sin conventional format
Section titled “Commit sin conventional format”# ❌ CommitLint lo rechazarágit commit -m "arreglé el bug del login"git commit -m "Updated styles"git commit -m "WIP"
# ✅ Conventional commitsgit commit -m "fix(auth): resolve token refresh loop"git commit -m "style(login): adjust form spacing on mobile"git commit -m "feat(checkout): add promo code input"Push directo a main
Section titled “Push directo a main”# ❌ NUNCA — main está protegido, usa PRgit push origin main
# ✅ Crea branch y PRgit checkout -b 123-mi-featuregit push origin 123-mi-feature# → Crear PR en GitHubResumen: Si no estás seguro…
Section titled “Resumen: Si no estás seguro…”| Duda | Respuesta |
|---|---|
| ¿Dónde pongo la lógica de datos? | En un servicio (services/) |
| ¿Dónde pongo lógica reutilizable? | En un composable (composables/) |
| ¿Cómo obtengo datos del backend? | useServices('nombre') |
| ¿Cómo hago una alerta? | useShowAlert().showAlert('tipo', 'msg') |
| ¿Cómo añado un color? | CSS variable + tailwind.config.ts |
¿Puedo usar any? | No. Usa unknown y narrow. |
| ¿Puedo console.log? | Solo durante debug. Eliminar antes de commit. |