Proyecto Final

Desarrollo de un Proyecto Completo

Como proyecto final del curso, vamos a desarrollar una aplicación completa en Vue.js. Esta aplicación será una SPA (Single Page Application) que permite a los usuarios gestionar una lista de tareas (To-Do List) con autenticación básica. Utilizaremos Vue Router para la navegación, Vuex para la gestión del estado, y Axios para interactuar con una API.

Funcionalidades del Proyecto

  1. Autenticación de Usuarios:

    • Registro
    • Inicio de sesión
    • Cierre de sesión
  2. Gestión de Tareas:

    • Crear una nueva tarea
    • Editar una tarea
    • Eliminar una tarea
    • Marcar una tarea como completada
  3. Navegación entre Vistas:

    • Página de inicio
    • Página de tareas
    • Página de perfil

Estructura del Proyecto

				
					src/
|-- api/
|   |-- api.js
|-- components/
|   |-- TaskList.vue
|   |-- TaskItem.vue
|-- store/
|   |-- index.js
|   |-- auth.js
|   |-- tasks.js
|-- views/
|   |-- Home.vue
|   |-- Login.vue
|   |-- Register.vue
|   |-- Tasks.vue
|   |-- Profile.vue
|-- App.vue
|-- main.js
|-- router.js

				
			

Paso 1: Configuración Inicial

Instalar Dependencias

Primero, necesitamos instalar las siguientes dependencias:

				
					npm install vue-router@next vuex@next axios

				
			

Configuración de Axios

				
					// src/api/api.js
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://example.com/api',
  withCredentials: true,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json'
  }
});

export default apiClient;

				
			

Configuración de Vue Router

				
					// src/router.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from './views/Home.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';
import Tasks from './views/Tasks.vue';
import Profile from './views/Profile.vue';

const routes = [
  { path: '/', component: Home },
  { path: '/login', component: Login },
  { path: '/register', component: Register },
  { path: '/tasks', component: Tasks, meta: { requiresAuth: true } },
  { path: '/profile', component: Profile, meta: { requiresAuth: true } }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

router.beforeEach((to, from, next) => {
  const loggedIn = localStorage.getItem('user');

  if (to.matched.some(record => record.meta.requiresAuth) && !loggedIn) {
    next('/login');
  } else {
    next();
  }
});

export default router;
on',
    'Content-Type': 'application/json'
  }
});

export default apiClient;

				
			

Paso 2: Configuración de Vuex

Configuración Inicial de Vuex

				
					// src/store/index.js
import { createStore } from 'vuex';
import auth from './auth';
import tasks from './tasks';

const store = createStore({
  modules: {
    auth,
    tasks
  }
});

export default store;

				
			

Módulo de Autenticación

				
					// src/store/auth.js
import apiClient from '../api/api';

const state = {
  user: JSON.parse(localStorage.getItem('user')) || null
};

const mutations = {
  setUser(state, user) {
    state.user = user;
  },
  logout(state) {
    state.user = null;
  }
};

const actions = {
  async login({ commit }, credentials) {
    const response = await apiClient.post('/login', credentials);
    const user = response.data;
    localStorage.setItem('user', JSON.stringify(user));
    commit('setUser', user);
  },
  async register({ commit }, userData) {
    const response = await apiClient.post('/register', userData);
    const user = response.data;
    localStorage.setItem('user', JSON.stringify(user));
    commit('setUser', user);
  },
  logout({ commit }) {
    localStorage.removeItem('user');
    commit('logout');
  }
};

const getters = {
  isAuthenticated: state => !!state.user
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};

				
			

Módulo de Tareas

				
					// src/store/tasks.js
import apiClient from '../api/api';

const state = {
  tasks: []
};

const mutations = {
  setTasks(state, tasks) {
    state.tasks = tasks;
  },
  addTask(state, task) {
    state.tasks.push(task);
  },
  updateTask(state, updatedTask) {
    const index = state.tasks.findIndex(task => task.id === updatedTask.id);
    if (index !== -1) {
      state.tasks.splice(index, 1, updatedTask);
    }
  },
  deleteTask(state, taskId) {
    state.tasks = state.tasks.filter(task => task.id !== taskId);
  }
};

const actions = {
  async fetchTasks({ commit }) {
    const response = await apiClient.get('/tasks');
    commit('setTasks', response.data);
  },
  async addTask({ commit }, task) {
    const response = await apiClient.post('/tasks', task);
    commit('addTask', response.data);
  },
  async updateTask({ commit }, task) {
    const response = await apiClient.put(`/tasks/${task.id}`, task);
    commit('updateTask', response.data);
  },
  async deleteTask({ commit }, taskId) {
    await apiClient.delete(`/tasks/${taskId}`);
    commit('deleteTask', taskId);
  }
};

const getters = {
  allTasks: state => state.tasks,
  completedTasks: state => state.tasks.filter(task => task.completed),
  incompleteTasks: state => state.tasks.filter(task => !task.completed)
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};

				
			

Paso 3: Componentes y Vistas

Componente TaskList

				
					<!-- src/components/TaskList.vue -->
<template>
  <div>
    <h2>Lista de Tareas</h2>
    <ul>
      <li v-for="task in tasks" :key="task.id">
        <TaskItem :task="task" />
      </li>
    </ul>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';
import TaskItem from './TaskItem.vue';

export default {
  components: { TaskItem },
  computed: {
    ...mapGetters('tasks', ['allTasks'])
  },
  mounted() {
    this.$store.dispatch('tasks/fetchTasks');
  }
};
</script>

				
			

Componente TaskItem

				
					<!-- src/components/TaskItem.vue -->
<template>
  <div>
    <input type="checkbox" v-model="task.completed" @change="toggleCompletion">
    <span :class="{ completed: task.completed }">{{ task.title }}</span>
    <button @click="deleteTask">Eliminar</button>
  </div>
</template>

<script>
export default {
  props: ['task'],
  methods: {
    toggleCompletion() {
      this.$store.dispatch('tasks/updateTask', this.task);
    },
    deleteTask() {
      this.$store.dispatch('tasks/deleteTask', this.task.id);
    }
  }
};
</script>

<style>
.completed {
  text-decoration: line-through;
}
</style>

				
			

Vistas

Home.vue

				
					<!-- src/views/Home.vue -->
<template>
  <div>
    <h1>Bienvenido a la Lista de Tareas</h1>
    <router-link to="/tasks">Ver Tareas</router-link>
  </div>
</template>

<script>
export default {
  name: 'Home'
};
</script>

				
			

Login.vue

				
					<!-- src/views/Login.vue -->
<template>
  <div>
    <h1>Iniciar Sesión</h1>
    <form @submit.prevent="login">
      <input v-model="email" type="email" placeholder="Email" required>
      <input v-model="password" type="password" placeholder="Password" required>
      <button type="submit">Login</button>
    </form>
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: '',
      password: '',
      error: ''
    };
  },
  methods: {
    async login() {
      try {
        await this.$store.dispatch('auth/login', {
          email: this.email,
          password: this.password
        });
        this.$router.push('/tasks');
      } catch {
        this.error = 'Error al iniciar sesión.';
      }
    }
  }
};
</script>

				
			

Register.vue

				
					<!-- src/views/Register.vue -->
<template>
  <div>
    <h1>Registrarse</h1>
    <form @submit.prevent="register">
      <input v-model="email" type="email" placeholder="Email" required>
      <input v-model="password" type="password" placeholder="Password" required>
      <button type="submit">Registrarse</button>
    </form>
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: '',
      password: '',
      error: ''
    };
  },
  methods: {
    async register() {
      try {
        await this.$store.dispatch('auth/register', {
          email: this.email,
          password: this.password
        });
        this.$router.push('/tasks');
      } catch {
        this.error = 'Error al registrarse.';
      }
    }
  }
};
</script>

				
			

Tasks.vue

				
					<!-- src/views/Tasks.vue -->
<template>
  <div>
    <h1>Mis Tareas</h1>
    <TaskList />
    <form @submit.prevent="addTask">
      <input v-model="title" placeholder="Nueva Tarea">
      <button type="submit">Añadir</button>
    </form>
  </div>
</template>

<script>
import TaskList from '../components/TaskList.vue';

export default {
  components: { TaskList },
  data() {
    return {
      title: ''
    };
  },
  methods: {
    addTask() {
      if (this.title) {
        this.$store.dispatch('tasks/addTask', { title: this.title, completed: false });
        this.title = '';
      }
    }
  }
};
</script>

				
			

Profile.vue

				
					<!-- src/views/Profile.vue -->
<template>
  <div>
    <h1>Perfil</h1>
    <p>Usuario: {{ user.email }}</p>
    <button @click="logout">Cerrar Sesión</button>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState('auth', ['user'])
  },
  methods: {
    logout() {
      this.$store.dispatch('auth/logout');
      this.$router.push('/login');
    }
  }
};
</script>

				
			

Paso 4: Configuración y Montaje de la Aplicación

Configuración de la Aplicación

				
					// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');

				
			

Resumen del Proyecto

Hemos desarrollado una aplicación completa en Vue.js que incluye:

  1. Autenticación de Usuarios: Funcionalidades de registro, inicio de sesión y cierre de sesión.
  2. Gestión de Tareas: Crear, editar, eliminar y marcar tareas como completadas.
  3. Navegación entre Vistas: Implementada con Vue Router, con protección de rutas.
  4. Gestión del Estado: Utilizando Vuex para manejar el estado global de la aplicación.
  5. Interacción con una API: Realizada con Axios para manejar peticiones HTTP.