diff --git a/AGENTS.md b/AGENTS.md index 78fb193..e551053 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,14 +13,19 @@ ``` src/ ├── assets/ # Imágenes, iconos estáticos -├── components/ # Componentes Vue reutilizables +├── components/ # Componentes Vue reutilizables ├── layouts/ # Layouts de página -├── pages/ # Vistas (auto-routed desde文件名) -├── plugins/ # Configuración de Vuetify, etc. -├── router/ # Configuración de rutas -├── services/ # API services (auth.js, etc.) -├── stores/ # Pinia stores -└── styles/ # SCSS settings +├── pages/ # Vistas (auto-routed desde文件名) +├── 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 @@ -51,6 +56,7 @@ import MiComponente from '@/components/MiComponente.vue'; - 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/` @@ -73,9 +79,36 @@ npm run lint # ESLint fix ## Git Commits **Antes de hacer commit:** -1. Pedir permiso al usuario +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 `#` 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 `) +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 diff --git a/src/components/CodeDialog.vue b/src/components/CodeDialog.vue deleted file mode 100644 index 5158c11..0000000 --- a/src/components/CodeDialog.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 0f2f5ef..2ca0199 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -13,11 +13,37 @@ - Logout + + + + {{ user?.username }} + {{ user?.email }} + + + + {{ user?.first_name }} {{ user?.last_name }} + + + + {{ user?.role === 'administrator' ? 'Administrador' : 'Usuario' }} + + + + + + mdi-logout + Cerrar sesión + + + + + mdi-account + {{ user?.username }} - - - + + + import trytonIcon from '../assets/icons/tryton-icon.svg'; import AuthService from '@/services/auth'; + import { useAuthStore } from '@/stores/auth'; + import { inject } from 'vue'; export default { name: 'NavBar', + setup() { + const authStore = useAuthStore(); + return { authStore }; + }, data: () => ({ drawer: false, group: null, showAdminMenu: false, isAuthenticated: false, + user: null, + api: inject('api'), menuItems: [ { title: 'Inicio', route: '/', icon: 'mdi-home'}, { title: 'Comprar', route:'/comprar', icon: 'mdi-cart'}, @@ -73,22 +107,41 @@ { title: 'Actualizar Clientes De Tryton', route: '/sincronizar_clientes_tryton', icon: 'trytonIcon'}, { title: 'Actualizar Ventas Tryton', route: '/sincronizar_ventas_tryton', icon: 'trytonIcon'} ], - }), - mounted() { + }), + computed: { + isAdmin() { + return this.user?.role === 'administrator'; + } + }, + mounted() { this.checkAuth(); + if (this.isAuthenticated) { + this.fetchUser(); + } }, - watch: { - group () { - this.drawer = false - }, - $route() { - this.checkAuth(); - }, - }, + watch: { + group () { + this.drawer = false + }, + $route() { + this.checkAuth(); + if (this.isAuthenticated && !this.user) { + this.fetchUser(); + } + }, + }, methods: { checkAuth() { this.isAuthenticated = AuthService.isAuthenticated(); }, + async fetchUser() { + try { + this.user = await this.api.getCurrentUser(); + this.authStore.setUser(this.user); + } catch (error) { + console.error('Error fetching user:', error); + } + }, navigate(route) { this.$router.push(route); }, @@ -100,10 +153,12 @@ this.showAdminMenu = !this.showAdminMenu; }, logout() { - AuthService.logout(); - this.isAuthenticated = false; - this.$router.push('/'); - }, + AuthService.logout(); + this.isAuthenticated = false; + this.user = null; + this.authStore.clearUser(); + this.$router.push('/'); + }, } } diff --git a/src/pages/compra_admin.vue b/src/pages/compra_admin.vue index b871718..aef0751 100644 --- a/src/pages/compra_admin.vue +++ b/src/pages/compra_admin.vue @@ -1,20 +1,19 @@ diff --git a/src/pages/cuadrar_tarro.vue b/src/pages/cuadrar_tarro.vue index fb60ff4..9035119 100644 --- a/src/pages/cuadrar_tarro.vue +++ b/src/pages/cuadrar_tarro.vue @@ -1,20 +1,19 @@ diff --git a/src/pages/cuadres_de_tarro.vue b/src/pages/cuadres_de_tarro.vue index 739a1e9..8678601 100644 --- a/src/pages/cuadres_de_tarro.vue +++ b/src/pages/cuadres_de_tarro.vue @@ -1,20 +1,19 @@ diff --git a/src/pages/sincronizar_clientes_tryton.vue b/src/pages/sincronizar_clientes_tryton.vue index 510f814..625c874 100644 --- a/src/pages/sincronizar_clientes_tryton.vue +++ b/src/pages/sincronizar_clientes_tryton.vue @@ -1,8 +1,5 @@ + diff --git a/src/pages/sincronizar_productos_tryton.vue b/src/pages/sincronizar_productos_tryton.vue index adec5f6..fcd74b0 100644 --- a/src/pages/sincronizar_productos_tryton.vue +++ b/src/pages/sincronizar_productos_tryton.vue @@ -1,8 +1,5 @@ + diff --git a/src/pages/sincronizar_ventas_tryton.vue b/src/pages/sincronizar_ventas_tryton.vue index a6359ed..35fc356 100644 --- a/src/pages/sincronizar_ventas_tryton.vue +++ b/src/pages/sincronizar_ventas_tryton.vue @@ -1,8 +1,5 @@ diff --git a/src/services/api.js b/src/services/api.js index 1bc6b51..cd9c455 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -31,10 +31,6 @@ class Api { return this.apiImplementation.getReconciliation(reconciliationId); } - isValidAdminCode(code) { - return this.apiImplementation.isValidAdminCode(code); - } - createPurchase(purchase) { return this.apiImplementation.createPurchase(purchase); } @@ -62,6 +58,10 @@ class Api { sendSalesToTryton(){ return this.apiImplementation.sendSalesToTryton(); } + + getCurrentUser() { + return this.apiImplementation.getCurrentUser(); + } } export default Api; diff --git a/src/services/django-api.js b/src/services/django-api.js index b302065..b44c5e3 100644 --- a/src/services/django-api.js +++ b/src/services/django-api.js @@ -49,11 +49,6 @@ class DjangoApi { return this.getRequest(url); } - isValidAdminCode(code) { - const url = this.base + `/don_confiao/api/admin_code/validate/${code}` - return this.getRequest(url) - } - createPurchase(purchase) { const url = this.base + '/don_confiao/api/sales/'; return this.postRequest(url, purchase); @@ -88,6 +83,11 @@ class DjangoApi { const url = this.base + '/don_confiao/api/enviar_ventas_a_tryton'; return this.postRequest(url, {}); } + + getCurrentUser() { + const url = this.base + '/api/users/me/'; + return this.getRequest(url); + } } export default DjangoApi; diff --git a/src/stores/auth.js b/src/stores/auth.js new file mode 100644 index 0000000..a9b373d --- /dev/null +++ b/src/stores/auth.js @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' + +export const useAuthStore = defineStore('auth', { + state: () => ({ + user: null + }), + getters: { + isAdmin: (state) => state.user?.role === 'administrator', + isAuthenticated: (state) => !!state.user + }, + actions: { + setUser(user) { + this.user = user + }, + clearUser() { + this.user = null + } + } +})