diff --git a/src/components/Login.vue b/src/components/Login.vue
new file mode 100644
index 0000000..b499f04
--- /dev/null
+++ b/src/components/Login.vue
@@ -0,0 +1,42 @@
+
+
+
+ Login
+
+
+
+ Entrar
+ {{ error }}
+
+
+ Cargar clientes
+
diff --git a/src/pages/autenticarse.vue b/src/pages/autenticarse.vue
new file mode 100644
index 0000000..d6a52a2
--- /dev/null
+++ b/src/pages/autenticarse.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/pages/login.vue b/src/pages/login.vue
new file mode 100644
index 0000000..d6a52a2
--- /dev/null
+++ b/src/pages/login.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/services/auth.js b/src/services/auth.js
new file mode 100644
index 0000000..8700ff3
--- /dev/null
+++ b/src/services/auth.js
@@ -0,0 +1,49 @@
+class AuthService {
+ static TOKEN_KEY = 'access_token';
+ static REFRESH_KEY = 'refresh_token';
+
+ static login(credentials) {
+ const url = `${import.meta.env.VITE_DJANGO_BASE_URL}/api/token/`;
+ return fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(credentials),
+ })
+ .then(r => r.json())
+ .then(data => {
+ localStorage.setItem(this.TOKEN_KEY, data.access);
+ localStorage.setItem(this.REFRESH_KEY, data.refresh);
+ return data;
+ });
+ }
+
+ static getAccessToken() {
+ return localStorage.getItem(this.TOKEN_KEY);
+ }
+
+ static getRefreshToken() {
+ return localStorage.getItem(this.REFRESH_KEY);
+ }
+
+ static async refresh() {
+ const refresh = this.getRefreshToken();
+ if (!refresh) throw new Error('No refresh token');
+
+ const url = `${import.meta.env.VITE_DJANGO_BASE_URL}/api/token/refresh/`;
+ const resp = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ refresh }),
+ });
+ const data = await resp.json();
+ localStorage.setItem(this.TOKEN_KEY, data.access);
+ return data.access;
+ }
+
+ static logout() {
+ localStorage.removeItem(this.TOKEN_KEY);
+ localStorage.removeItem(this.REFRESH_KEY);
+ }
+}
+
+export default AuthService;
diff --git a/src/services/django-api.js b/src/services/django-api.js
index 653ad3f..a52cd3b 100644
--- a/src/services/django-api.js
+++ b/src/services/django-api.js
@@ -1,8 +1,38 @@
+import AuthService from '@/services/auth';
+
class DjangoApi {
constructor() {
this.base = import.meta.env.VITE_DJANGO_BASE_URL;
}
+ _authHeaders() {
+ const token = AuthService.getAccessToken();
+ return token ? { Authorization: `Bearer ${token}` } : {};
+ }
+
+ _handleResponse(response) {
+ if (!response.ok) {
+ // Si el token ha expirado (401) intentamos refrescar y re‑intentar
+ if (response.status === 401) {
+ return AuthService.refresh().then(newToken => {
+ // volver a ejecutar la petición original con el nuevo token
+ const retryHeaders = {
+ ...response.headers,
+ Authorization: `Bearer ${newToken}`,
+ };
+ // aquí usamos fetch de nuevo con los mismos parámetros
+ // (para simplificar, delegamos a getRequest/postRequest)
+ // En la práctica, extrae la lógica a una función reutilizable.
+ throw new Error('Retry logic should be implemented here');
+ });
+ }
+ return response.json().then(err => {
+ throw new Error(`Error ${response.status}: ${err.detail || response.statusText}`);
+ });
+ }
+ return response.json();
+ }
+
getCustomers() {
const url = this.base + '/don_confiao/api/customers/';
return this.getRequest(url);
@@ -79,42 +109,23 @@ class DjangoApi {
}
getRequest(url) {
- return new Promise ((resolve, reject) => {
- fetch(url)
- .then(response => response.json())
- .then(data => {
- resolve(data);
- })
- .catch(error => {
- reject(error);
- });
- });
+ return fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...this._authHeaders(),
+ },
+ }).then(this._handleResponse);
}
postRequest(url, content) {
- return new Promise((resolve, reject) => {
- fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(content)
- })
- .then(response => {
- if (!response.ok) {
- reject(new Error(`Error ${response.status}: ${response.statusText}`));
- } else {
- response.json().then(data => {
- if (!data) {
- reject(new Error('La respuesta no es un JSON válido'));
- } else {
- resolve(data);
- }
- });
- }
- })
- .catch(error => reject(error));
- });
+ return fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...this._authHeaders(),
+ },
+ body: JSON.stringify(content),
+ }).then(this._handleResponse);
}
}
diff --git a/vite.config.mjs b/vite.config.mjs
index a1a9dec..3d70eca 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -11,6 +11,16 @@ import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
+const aliasMap = {
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
+};
+
+console.log('🔧 Alias configurado:');
+console.log(aliasMap); // <-- se muestra en la terminal al iniciar Vite
+console.log('Resolución real de "@":', aliasMap['@']);
+
+
+
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
@@ -50,7 +60,7 @@ export default defineConfig({
} },
resolve: {
alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url))
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
},
extensions: [
'.js',