184 lines
14 KiB
Markdown
184 lines
14 KiB
Markdown
# 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/*.vue` se routing automáticamente via `unplugin-vue-router`
|
|
- Alias `@` = `src/`
|
|
|
|
### Pages (CRITICAL)
|
|
**Siempre importar componentes en los archivos de página:**
|
|
```vue
|
|
<template>
|
|
<MiComponente />
|
|
</template>
|
|
|
|
<script setup>
|
|
import MiComponente from '@/components/MiComponente.vue';
|
|
</script>
|
|
```
|
|
|
|
### Componentes
|
|
- Usar Composition API (`<script setup>` o `export default { }`)
|
|
- Naming: PascalCase (ej: `LoginDialog.vue`, `CartGrid.vue`)
|
|
- Componentes de página van en `pages/`, componentes reutilizables en `components/`
|
|
|
|
### 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 con `inject('api')`
|
|
|
|
### Routing
|
|
- Rutas automáticas basadas en archivos en `src/pages/` (no se registran rutas a mano, excepto en casos especiales)
|
|
- `router/index.js` usa `setupLayouts(routes)` + guard `beforeEach`:
|
|
- Meta `requiresAuth` → redirige a `/autenticarse` si no hay token
|
|
- Meta `requiresAdmin` (o rutas en `ADMIN_ROUTES`) → redirige si el usuario no es admin
|
|
- **Rutas públicas** (ej: `/pedido/:code?`) NO deben llevar `requiresAuth`
|
|
|
|
## Environment Variables
|
|
- `VITE_DJANGO_BASE_URL` - URL del backend Django
|
|
- `VITE_API_IMPLEMENTATION` - Selecciona la implementación de API (default: django)
|
|
|
|
## Commands
|
|
```bash
|
|
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.js` y muchos `.vue`
|
|
- **4 espacios + semicolons + comillas dobles**: la mayoría de `services/` y otros `.vue`
|
|
|
|
### Config de ESLint
|
|
- `.eslintrc.js` está **versionado** y extiende `vuetify` (estilo **StandardJS**)
|
|
- Tiene `ignorePatterns` masivo: `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.mjs` requiere `server.deps.inline: ['vuetify']`** para montar componentes Vuetify en jsdom
|
|
- Los tests **no usan globals**: importar `describe/it/expect/vi` explícitamente desde `vitest`
|
|
- Tests de páginas con router: esperar a que el router actualice `route.params` con un helper (`waitForRouteParam`)
|
|
- Los `.d.ts` generados (`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á en `wrapper.text()`. Assertar sobre `document.body.textContent` (ver `tests/unit/components/provenance/ProvenanceDetailModal.spec.js`). Helpers para interactuar con inputs/buttons de diálogos en `tests/unit/components/provenance/admin/helpers.js` (`setBodyInput`, `clickBody`)
|
|
- **Selects/autocompletados en tests**: escribir en su `<input>` NO cambia el `v-model`. Emitir `update:modelValue` sobre el componente (`findAllComponents({ name: 'VSelect' })` / `{ name: 'VAutocomplete' }`). OJO: `VAutocomplete` también matchea como `VSelect`, y `v-data-table` añade un `VSelect` (items-per-page); filtrar por `props('items')` cuando haya varios
|
|
- **Listas largas** (municipios, proveedores): `v-autocomplete` con búsqueda, pero la selección debe conservarse aunque el filtro no la incluya → `items` computado que devuelve los filtrados + los seleccionados que no estén (patrón en `SuppliersManagement.vue`, `GeographyManagement.vue`, `SupplierLinkDialog.vue`)
|
|
|
|
## Common Issues
|
|
1. **Página en blanco:** Verificar que los componentes en `src/pages/*.vue` tengan import explícito
|
|
2. **`npm run build` falla con `EACCES`:** `dist/` tiene archivos root (docker, gitignored) y no se puede borrar. Verificar el build con `npx vite build --outDir /tmp/opencode/dist-check`
|
|
3. **Build falla con "Illegal '/' in tags" / "Invalid end tag":** tags de `CurrencyText` malformados pre-existentes (ej: `<CurrencyText <:value="..."/CurrencyText >` o `</CurrencyText>` duplicado). Buscar `CurrencyText` mal cerrado en `ReconciliationJar.vue` / `ReconciliationJarView.vue`
|
|
4. **Después de mergear `main`:** correr `npm install` — las deps nuevas (ej: `leaflet` en `StoreLocation.vue`) quedan en `package.json` pero no instaladas → el build falla con "Rollup failed to resolve import"
|
|
5. **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:**
|
|
1. **SIEMPRE pedir permiso al usuario antes de hacer commit**
|
|
2. Mostrar resumen de los cambios que se incluirán
|
|
|
|
**Formato de mensajes:**
|
|
- Usar prefijo `#<numero>` para referenciar el issue (ej: `#28 feat: add login` donde #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
|
|
1. **Login:** `AuthService.login(credentials)` → obtiene JWT tokens → guarda en localStorage
|
|
2. **Token:** Se envía en headers via interceptor en `http.js` (`Authorization: Bearer <token>`)
|
|
3. **Refresh:** El interceptor renueva automáticamente el token si expira (401)
|
|
4. **Logout:** `AuthService.logout()` → limpia localStorage
|
|
|
|
### Estructura de API
|
|
- `api.js`: Interfaz genérica con métodos como `getCustomers()`, `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, SIN `api/`)
|
|
|
|
## Consulta Pública de Pedidos
|
|
- Ruta `/pedido/:code?` (pública, sin `requiresAuth`)
|
|
- `getPublicOrderSummary(code)` en `services/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.vue` se comparte con `SummaryPurchase.vue`
|
|
|
|
## Provenance (Origen e historia de los productos)
|
|
- Los gráficos se renderizan en el resumen público (`ProvenanceSection` dentro de `PublicOrderSummary.vue`) a partir de `product_provenance` embebido 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 (props `nodes`, `edges`, `height`, `options`; import `import { DataSet, Network } from 'vis-network/standalone'`; emite `select` con el nodo). **OJO**: nodo con `image: null` → TypeError de vis; omitir la clave `image` si no hay foto
|
|
- **Específico público** (`src/components/provenance/`): builder puro en `provenance-graph.js` (`buildProvenanceGraph(provenance, kinds)`, `hasAnySupplier`) y el adaptador vis `provenance-vis.js` (`toVisNodes`, `toVisEdges`, `chartOptions`). **Semántica de certeza**: arista con `certain: true` es continua (inequívoca) y `certain: false` es discontinua (dudosa); con varios proveedores por producto se inserta un nodo `junction:<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). Las aristas se deduplican por par `(from, to)` y si un mismo par repite con distinta certeza gana la duda. `buildProvenanceGraph` acepta los niveles a graficar (`product`, `supplier`, `organization`, `municipality`, `department`, `country`); los niveles omitidos se saltan conectando el nivel previo con el siguiente. `ProvenanceGraph.vue` unifica los charts en uno con checkboxes de filtro (por defecto solo productos y proveedores), leyenda con el color de cada nivel (`KIND_COLORS` en `provenance-vis.js`), columnas por nivel (x fijo por tipo; la física ordena la y) y espaciado vertical mínimo (`minVerticalSpacing` en `VisChart`); muestra "próximamente estará disponible" cuando no hay relaciones. `ProvenanceSection.vue` muestra el título ("Origen de los productos") con un desplegable (clic en el título o botón chevron) que oculta el gráfico por defecto, y un segundo desplegable para el mapa ("Mapa de origen de los productos") — patrón reutilizable para futuros bloques. `ProvenanceMap.vue` muestra un recuadro informativo (lista) con **todos** los productos del payload —incluidos los sin proveedor o cuyo municipio no tiene coordenadas, marcados "Sin geolocalización"— y, si hay al menos un municipio con coordenadas, el mapa leaflet debajo: un marcador por producto en el municipio de origen (usa `municipality.latitude/longitude` del payload, sin desplazar posiciones aunque coincidan; los productos del mismo punto se agrupan en un único marcador con contador que al hacer clic despliega un popup con la lista de productos internos para abrir cada uno) y un ícono de persona en la posición de la tienda (settings store, endpoint público `getStoreSettings`); `fitBounds` abarca todos los marcadores, al hacer hover sobre un producto dibuja una línea discontinua hasta la tienda y al hacer hover sobre la tienda dibuja las de todos los productos; clic en un marcador individual abre `ProvenanceRelationModal.vue` (producto + proveedor + organización + territorio). Clic en un producto del recuadro: si tiene ubicación hace `flyTo` al punto y abre el diálogo; si no, solo abre el diálogo (que para productos sin geolocalización muestra el proveedor/organización disponibles y la nota "Aún sin geolocalización registrada.", y para productos sin proveedor la nota "Aún no se ha vinculado un proveedor a este producto."). `ProvenanceDetailModal.vue`
|
|
- **Admin CRUD** (`src/components/provenance/admin/`): `OrganizationsManagement.vue`, `SuppliersManagement.vue`, `GeographyManagement.vue` (tabs países/departamentos/municipios), `SupplierLinkDialog.vue` (vincula productos↔proveedores, abierto desde `ProductsManagement.vue`). Páginas en `src/pages/admin/{organizations,suppliers,geography}.vue`; rutas en `ADMIN_ROUTES` (`router/index.js`); ítems en `NavBar.vue`
|
|
- **Endpoints provenance**: `/don_confiao/api/organizations/`, `/suppliers/`, `/countries/`, `/departments/`, `/municipalities/` (CRUD); vincular productos con `PATCH /don_confiao/api/products/<id>/` body `{"suppliers": [ids]}`; detalle de producto (con `suppliers`) via `GET /don_confiao/api/products/<id>/`
|
|
- Los tests mockean `vis-network/standalone` (`vi.mock('vis-network/standalone', ...)`) o el propio `VisChart.vue`, y la API con `global.provide: { api }`
|