Saltar al contenido principal

Guía de Desarrollo Frontend

· 5 min de lectura
Software Bugs generator @ sils.tech

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

Pendientes de agregar

Manejo de Estados

Routing (Vue Router)

Caching Strategies

Performance Optimization

Seguridad

Testing

Ciclo de Vida de Vue

Accesibilidad (a11y)