Guía de Desarrollo Frontend
Esta guía establece los patrones arquitectónicos y criterios de desarrollo utilizados en el Dashboard General. Sirve como referencia para mantener consistencia en el desarrollo de nuevas features o vistas similares.
Nota Esta guía está orientada a Vue 2. Se incluyen menciones a cambios en Vue 3 solo como referencia.
1. Arquitectura de Componentes
Componentes UI Puros
Componentes que solo tienen vista y no necesitan lógica de negocio.
Características
- No hacen API calls
- No tienen lógica compleja de transformación de datos
- Reciben datos mediante props y emiten eventos simples
- Son altamente reutilizables
Criterio de desarrollo: Si cumple con las características anteriores → desarrollar y migrar a Sils Design System
<MetricCard
title="Flota Activa"
subtitle="Unidades"
:value="activeFleet.total"
:error="activeFleet.error"
>
<template #icon>
<img :src="getPosicionFloatIcon" />
</template>
</MetricCard>
Componentes UI + Lógica de Negocio
Componentes independientes que combinan UI con lógica específica reutilizable. Pueden ser la combinación de componentes de sils desing + la logica.
Características
- Manejan su propia lógica de negocio (API calls, validaciones, etc.)
- Gestionan sus propios estados de loading y error
- Son reutilizables en múltiples vistas
- No dependen del contexto del componente padre para funcionar
Cuándo crear estos componentes:
- La lógica se repite en múltiples vistas
- El componente tiene un propósito claro y delimitado
- Puede funcionar de manera autónoma
<TransporterFilter
:value="transporters"
:business-id="business ? business.value : null"
@input="handleTransportersChange"
@has-transporters-change="handleHasTransportersChange"
/>
Nota: TransporterFilter maneja su lógica de negocio de manera autónoma.
Comunicación entre Componentes - Patrón Emitter
Cuándo usar eventos ($emit)
- Componente hijo necesita notificar al padre de una acción
- Flujo de datos unidireccional (props down, events up)
- Componentes UI puros que delegan lógica al padre
Convención de nombres
Usar kebab-case para eventos:
// ❌ MAL
this.$emit('updateValue', newValue)
this.$emit('handleClick')
// ✅ BIEN
this.$emit('update:value', newValue)
this.$emit('click')
this.$emit('filter-changed', filters)
Patrón v-model custom (Vue 2)
<!-- Componente hijo -->
<script>
export default {
props: {
value: {
type: Array,
default: () => []
}
},
methods: {
handleChange(newValue) {
this.$emit('input', newValue)
}
}
}
</script>
<!-- Componente padre -->
<TransporterFilter v-model="selectedTransporters" />
Vue 3: Usa modelValue prop y update:modelValue event en lugar de value/input.
Eventos con payload estructurado
// ✅ BIEN - Payload descriptivo
this.$emit('filter-changed', {
transporters: [1, 2, 3],
business: 5,
dateRange: { start: '2025-01-01', end: '2025-01-07' }
})
// ❌ MAL - Múltiples parámetros sin estructura
this.$emit('filter-changed', [1,2,3], 5, '2025-01-01', '2025-01-07')
Otras opciones a agregar
- Emitir eventos con validación
- Nombrar handlers en padre
- Evitar event bus
2. Estrategia de Loading
Loading General
Cuándo usar:
- Cuando el cambio de un valor o filtro afecta a toda la información o gran parte de la vista
- En la carga inicial de la vista
- Al cambiar filtros principales que requieren recarga completa de datos
async onSearch() {
this.loading = true // ← Loading general
try {
await this.getAllICMData() // Afecta al gráfico ICM
await this.getAllChipsData() // Afecta a las 3 tarjetas métricas
} finally {
this.loading = false
}
}
Implementación
<Overlay :show="loading" overlay-variant="primary">
<!-- Todo el contenido -->
</Overlay>
Loading General
Cuándo usar:
- Cuando solo se actualiza una sección específica de la vista
- Al recargar datos de un componente sin afectar al resto
- Para operaciones secundarias (exportar, descargar, etc.)
Criterio clave: Si el usuario puede seguir interactuando con otras partes de la UI mientras algo se carga, usa loading individual.
Implementación FALTA
3. Error Handling
Niveles de manejo de errores
- Error general (toda la vista falló)
- Error parcial (un componente falló)
- Error inline (pequeño detalle falló)
Estructura de datos de error
Formato estandar
{
data: null, // o los datos si no hubo error
error: {
message: 'Error al cargar los datos',
type: 'network_error', // network_error | client_error | server_error
statusCode: 500,
originalError: error // Error original de axios (opcional)
}
}
En componente Vue
data() {
return {
activeFleet: {
total: 0,
error: { isError: false, message: '' }
}
}
}
Flujo de manejo de errores
async getAllChipsData() {
// 1. Reset errores previos
this.activeFleet.error = { isError: false, message: '' }
// 2. Ejecutar API calls en paralelo con Promise.allSettled
const [activeFleetResult, maintenanceResult] = await Promise.allSettled([
activeFleet.getCountVehiclesByTransportista({ /* ... */ }),
turnos.getCountManteinanceByTransporter({ /* ... */ })
])
// 3. Procesar resultados individuales
this.handleActiveFleetResult(activeFleetResult)
this.handleMaintenanceResult(maintenanceResult)
}
handleActiveFleetResult(result) {
const { hasError, data } = this.handleApiError(result, this.activeFleet.error)
if (!hasError) {
this.activeFleet.total = data
}
}
handleApiError(result, errorTarget) {
const hasError = result.status === 'rejected' || result.value?.error !== null
const errorMessage = result.status === 'rejected'
? 'Error al cargar los datos'
: (result.value?.error?.message || 'Error desconocido')
errorTarget.isError = hasError
errorTarget.message = hasError ? errorMessage : ''
if (hasError) console.error(errorMessage)
return { hasError, data: result.value?.data }
}
4. Lógica de Negocio Reutilizable
Funciones y utilidades que implementan lógica que se puede repetir en múltiples lugares.
Tipos de lógica reutilizable
- Error Boundary (manejo de errores)
- Helpers / Utils (de fechas, ids, etc.)
- Mixins (lógica de negocio)
- Formatters
- Validators
Vue 3: Los Mixins están deprecados en favor de Composables (Composition API).
todo Ampliar con ejemplos
5. Ciclo de Vida de la Vista
┌─────────────────────────────────────────────────────┐
│ 1. Inicio de carga │
│ → this.loading = true │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 2. API Calls │
│ → Promise.allSettled([...]) │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 3. Procesar resultados │
│ → this.loading = false │
└─────────────────┬───────────────────────────────────┘
│
┌─────────┴──────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ 4a. ¿Hay │ │ 4b. ¿Hay │
│ info? │ │ error? │
└───────┬───────┘ └───────┬───────┘
│ │
┌─────┴─────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌─────┐ ┌─────┐
│ Sí │ │ No │ │ Sí │ │ No │
└─┬──┘ └──┬─┘ └──┬──┘ └──┬──┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│Mostrar │ │Mostrar │ │Mostrar │ │Mostrar │
│ info │ │"No hay"│ │ error │ │ info │
│ │ │ info" │ │(dónde) │ │ │
└────────┘ └────────┘ └────────┘ └────────┘
TODOS
- Ampliar con ejemplos (Lógica de Negocio Reutilizable)
- Ejemplo o aclaraciones de cuando no
- actualizar formato errores cuando se corrija