12 KiB
12 KiB
Don Confiao - Frontend
Tech Stack
- Framework: Vue 3 (Composition API)
- UI Library: Vuetify 3
- Routing: Vue Router 4 (auto-routes con
unplugin-vue-router) - State: Pinia
- HTTP: Axios
- Build: Vite
- Linting: ESLint
Project Structure
src/
├── assets/ # Imágenes, iconos estáticos
├── components/ # Componentes Vue reutilizables
│ ├── order/ # Componentes del resumen de pedido público
│ ├── provenance/ # Provenance (público + admin): gráficos, sección y CRUD admin
│ │ └── admin/ # Organizaciones, Proveedores, Geografía, SupplierLinkDialog
│ └── graph/ # VisChart.vue (wrapper genérico de vis-network)
├── layouts/ # Layouts de página
├── pages/ # Vistas (auto-routed desde文件名)
│ └── admin/ # Páginas admin (products, organizations, suppliers, geography, ...)
├── plugins/ # Configuración de Vuetify, etc.
├── router/ # Configuración de rutas
├── services/ # API services (auth.js, etc.)
│ ├── api.js # Clase wrapper que делегат methods
│ ├── api-implementation.js # Factory que selecciona implementación
│ ├── auth.js # Manejo de auth (login, tokens JWT)
│ ├── django-api.js # Implementación de API para Django
│ └── http.js # Axios instance con interceptors
├── stores/ # Pinia stores
└── styles/ # SCSS settings
Important Conventions
Auto-imports
- Componentes en
src/components/se auto-importan por nombre - Los archivos en
src/pages/*.vuese routing automáticamente viaunplugin-vue-router - Alias
@=src/
Pages (CRITICAL)
Siempre importar componentes en los archivos de página:
<template>
<MiComponente />
</template>
<script setup>
import MiComponente from '@/components/MiComponente.vue';
</script>
Componentes
- Usar Composition API (
<script setup>oexport default { }) - Naming: PascalCase (ej:
LoginDialog.vue,CartGrid.vue) - Componentes de página van en
pages/, componentes reutilizables encomponents/
Servicios API
- Ubicación:
src/services/ - Usar Axios para HTTP requests
- JWT tokens en localStorage (
access_token,refresh_token) - La API se inyecta globalmente via
app.provide('api', api)y se usa coninject('api')
Routing
- Rutas automáticas basadas en archivos en
src/pages/(no se registran rutas a mano, excepto en casos especiales) router/index.jsusasetupLayouts(routes)+ guardbeforeEach:- Meta
requiresAuth→ redirige a/autenticarsesi no hay token - Meta
requiresAdmin(o rutas enADMIN_ROUTES) → redirige si el usuario no es admin - Rutas públicas (ej:
/pedido/:code?) NO deben llevarrequiresAuth
- Meta
Environment Variables
VITE_DJANGO_BASE_URL- URL del backend DjangoVITE_API_IMPLEMENTATION- Selecciona la implementación de API (default: django)
Commands
npm run dev # Desarrollo (puerto 3000)
npm run preview # Preview build
npm run lint # ESLint --fix (¡OJO: reformatea archivos, ver sección Lint!)
npm test # Vitest (unit tests)
npm run test:watch
npx vite build --outDir /tmp/opencode/dist-check # Verificar build sin tocar dist/
Lint y Estilos (IMPORTANTE)
El repo mezcla DOS estilos JS (~50/50)
No hay un estilo mayoritario. El código histórico está partido:
- StandardJS (2 espacios, sin semicolons, comillas simples):
main.js,stores/*,plugins/*,router/index.js,services/http.jsy muchos.vue - 4 espacios + semicolons + comillas dobles: la mayoría de
services/y otros.vue
Config de ESLint
.eslintrc.jsestá versionado y extiendevuetify(estilo StandardJS)- Tiene
ignorePatternsmasivo:src/**excepto los archivos nuevos de la tarea (!src/components/order/**,!src/components/PublicOrderSummary.vue,!src/components/provenance/**,!src/components/graph/*.vue,!src/pages/pedido/**) - Los archivos nuevos deben seguir StandardJS para quedar lint-eados:
- 2 espacios (indent), sin semicolons, comillas simples
function () {}con espacio,const f = (x) => x(arrow-parens en args únicos NO)- Sin trailing commas;
{ clave: valor }con espacios internos - Eventos personalizados en kebab-case,
v-slot:nombre(no#nombre) - Atributos en orden alfabético dentro de su categoría (
vue/attributes-order): directivas (v-if,v-model) primero, luego props/attrs, luego@eventos
PELIGRO: npm run lint usa --fix
- Reformatea automáticamente TODO archivo no ignorado que no cumpla estilo
- Puede modificar decenas de archivos de golpe. Antes de usarlo revisar qué está ignorado
- Para evaluar sin modificar:
npx eslint . --ignore-path .gitignore(sin--fix) - Para desglosar por archivo/regla:
npx eslint . --ignore-path .gitignore --format json | node -e "..."
Testing (Vitest)
- Correr con
npm test(vitest run) - Infra en
tests/setup.js(polyfills:navigator.clipboard,ResizeObserver,IntersectionObserver,matchMedia) +vitest.config.mjs vitest.config.mjsrequiereserver.deps.inline: ['vuetify']para montar componentes Vuetify en jsdom- Los tests no usan globals: importar
describe/it/expect/viexplícitamente desdevitest - Tests de páginas con router: esperar a que el router actualice
route.paramscon un helper (waitForRouteParam) - Los
.d.tsgenerados (auto-imports.d.ts,components.d.ts,typed-router.d.ts) están en.gitignore - Diálogos Vuetify se teleportan a
document.body: en tests, el contenido NO está enwrapper.text(). Assertar sobredocument.body.textContent(vertests/unit/components/provenance/ProvenanceDetailModal.spec.js). Helpers para interactuar con inputs/buttons de diálogos entests/unit/components/provenance/admin/helpers.js(setBodyInput,clickBody) - Selects/autocompletados en tests: escribir en su
<input>NO cambia elv-model. Emitirupdate:modelValuesobre el componente (findAllComponents({ name: 'VSelect' })/{ name: 'VAutocomplete' }). OJO:VAutocompletetambién matchea comoVSelect, yv-data-tableañade unVSelect(items-per-page); filtrar porprops('items')cuando haya varios - Listas largas (municipios, proveedores):
v-autocompletecon búsqueda, pero la selección debe conservarse aunque el filtro no la incluya →itemscomputado que devuelve los filtrados + los seleccionados que no estén (patrón enSuppliersManagement.vue,GeographyManagement.vue,SupplierLinkDialog.vue)
Common Issues
- Página en blanco: Verificar que los componentes en
src/pages/*.vuetengan import explícito npm run buildfalla conEACCES:dist/tiene archivos root (docker, gitignored) y no se puede borrar. Verificar el build connpx vite build --outDir /tmp/opencode/dist-check- Build falla con "Illegal '/' in tags" / "Invalid end tag": tags de
CurrencyTextmalformados pre-existentes (ej:<CurrencyText <:value="..."/CurrencyText >o</CurrencyText>duplicado). BuscarCurrencyTextmal cerrado enReconciliationJar.vue/ReconciliationJarView.vue - Después de mergear
main: corrernpm install— las deps nuevas (ej:leafletenStoreLocation.vue) quedan enpackage.jsonpero no instaladas → el build falla con "Rollup failed to resolve import" - Errores de lint: ver sección Lint y Estilos (el repo no cumple un único estilo; no "arreglar" el lint de archivos pre-existentes)
Git Commits
Antes de hacer commit:
- SIEMPRE pedir permiso al usuario antes de hacer commit
- Mostrar resumen de los cambios que se incluirán
Formato de mensajes:
- Usar prefijo
#<numero>para referenciar el issue (ej:#28 feat: add logindonde #28 es el número del issue en GitHub/GitLab) - Prefijos válidos:
feat,fix,chore,docs,refactor,style
Análisis del Proyecto
Flujo de Autenticación
- Login:
AuthService.login(credentials)→ obtiene JWT tokens → guarda en localStorage - Token: Se envía en headers via interceptor en
http.js(Authorization: Bearer <token>) - Refresh: El interceptor renueva automáticamente el token si expira (401)
- Logout:
AuthService.logout()→ limpia localStorage
Estructura de API
api.js: Interfaz genérica con métodos comogetCustomers(),getProducts(), etc.api-implementation.js: Factory que selecciona implementación (actualmente solo Django)django-api.js: Implementación concreta con endpoints de Django
Componentes Principales
- NavBar.vue: Barra de navegación con menú de usuario
- LoginDialog.vue: Diálogo de inicio de sesión
- Purchase.vue / AdminPurchase.vue: Componentes de compra
- Cart.vue: Carrito de compras
- SummaryPurchase.vue: Resumen de compra
Endpoints Django Comunes
/api/token/- Autenticación (login/refresh)/users/me/- Usuario actual/don_confiao/api/customers/- Clientes/don_confiao/api/products/- Productos/don_confiao/api/sales/- Ventas/don_confiao/resumen_publico/<code>- Resumen público de pedido por código (AllowAny, SINapi/)
Consulta Pública de Pedidos
- Ruta
/pedido/:code?(pública, sinrequiresAuth) getPublicOrderSummary(code)enservices/api.js/django-api.js- Componentes modulares en
src/components/order/:OrderAccessInfo.vue(código + link),OrderCustomer.vue,OrderLines.vue,OrderPayment.vue,OrderTotal.vue - Orquestador:
PublicOrderSummary.vue;OrderAccessInfo.vuese comparte conSummaryPurchase.vue
Provenance (Origen e historia de los productos)
- Los gráficos se renderizan en el resumen público (
ProvenanceSectiondentro dePublicOrderSummary.vue) a partir deproduct_provenanceembebido en el resumen (GET /don_confiao/resumen_publico/<code>). El backend NO genera los gráficos. - Payload:
[{ product: {id, name, catalogue_images[]}, suppliers: [{ supplier: {...}, organization|null, municipality|null, department|null, country|null }] }]. - Genérico reutilizable:
src/components/graph/VisChart.vue: wrapper de vis-network (propsnodes,edges,height,options; importimport { DataSet, Network } from 'vis-network/standalone'; emiteselectcon el nodo). OJO: nodo conimage: null→ TypeError de vis; omitir la claveimagesi no hay foto
- Específico público (
src/components/provenance/): builders puros enprovenance-graph.js(buildSupplierOrganizationGraph,buildTerritoryGraph,hasAnySupplier,hasAnyTerritory) y el adaptador visprovenance-vis.js(toVisNodes,toVisEdges,chartOptions). Semántica de certeza: arista concertain: truees continua (inequívoca) ycertain: falsees discontinua (dudosa); con varios proveedores por producto se inserta un nodojunction:<productId>(disyunción) con arista sólida hasta él y discontinua hacia cada proveedor; la duda se corta donde los proveedores coinciden (misma organización/municipio/departamento). El gráfico de territorio va solo hasta departamento (sin país, por ahora Colombia). Las aristas se deduplican por par(from, to)y si un mismo par repite con distinta certeza gana la duda. Los charts (que muestran "próximamente estará disponible" cuando no hay relaciones),ProvenanceDetailModal.vueyProvenanceSection.vue - Admin CRUD (
src/components/provenance/admin/):OrganizationsManagement.vue,SuppliersManagement.vue,GeographyManagement.vue(tabs países/departamentos/municipios),SupplierLinkDialog.vue(vincula productos↔proveedores, abierto desdeProductsManagement.vue). Páginas ensrc/pages/admin/{organizations,suppliers,geography}.vue; rutas enADMIN_ROUTES(router/index.js); ítems enNavBar.vue - Endpoints provenance:
/don_confiao/api/organizations/,/suppliers/,/countries/,/departments/,/municipalities/(CRUD); vincular productos conPATCH /don_confiao/api/products/<id>/body{"suppliers": [ids]}; detalle de producto (consuppliers) viaGET /don_confiao/api/products/<id>/ - Los tests mockean
vis-network/standalone(vi.mock('vis-network/standalone', ...)) o el propioVisChart.vue, y la API conglobal.provide: { api }