diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js
new file mode 100644
index 0000000..4bb3d74
--- /dev/null
+++ b/backend/controllers/authController.js
@@ -0,0 +1,88 @@
+const { User } = require('../models');
+const bcrypt = require('bcryptjs');
+
+// @desc Register a new user
+// @route POST /api/auth/register
+// @access Public
+exports.register = async (req, res) => {
+ try {
+ const { name, email, password } = req.body;
+
+ // Check if user already exists
+ const userExists = await User.findOne({ where: { email } });
+ if (userExists) {
+ return res.status(400).json({
+ success: false,
+ message: 'El usuario ya existe'
+ });
+ }
+
+ // Create user
+ const user = await User.create({
+ name,
+ email,
+ password, // Will be hashed by the model hooks
+ profilePicUrl: '/uploads/default-avatar.jpg',
+ notificationsEnabled: true
+ });
+
+ // Return user without password
+ const userResponse = user.toJSON();
+ delete userResponse.password;
+
+ res.status(201).json({
+ success: true,
+ user: userResponse
+ });
+ } catch (error) {
+ console.error('Error en register:', error);
+ res.status(500).json({
+ success: false,
+ message: 'Error al registrar usuario',
+ error: error.message
+ });
+ }
+};
+
+// @desc Login user
+// @route POST /api/auth/login
+// @access Public
+exports.login = async (req, res) => {
+ try {
+ const { email, password } = req.body;
+
+ // Check if user exists
+ const user = await User.findOne({ where: { email } });
+ if (!user) {
+ return res.status(401).json({
+ success: false,
+ message: 'Credenciales inválidas'
+ });
+ }
+
+ // Check if password matches
+ const isMatch = await user.matchPassword(password);
+ if (!isMatch) {
+ return res.status(401).json({
+ success: false,
+ message: 'Credenciales inválidas'
+ });
+ }
+
+ // Return user without password
+ const userResponse = user.toJSON();
+ delete userResponse.password;
+
+ res.status(200).json({
+ success: true,
+ user: userResponse
+ });
+ } catch (error) {
+ console.error('Error en login:', error);
+ res.status(500).json({
+ success: false,
+ message: 'Error al iniciar sesión',
+ error: error.message
+ });
+ }
+};
\ No newline at end of file
diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js
new file mode 100644
index 0000000..14f2c16
--- /dev/null
+++ b/backend/routes/authRoutes.js
@@ -0,0 +1,9 @@
+const express = require('express');
+const { register, login } = require('../controllers/authController');
+
+const router = express.Router();
+
+router.post('/register', register);
+router.post('/login', login);
+
+module.exports = router;
\ No newline at end of file
diff --git a/backend/server.js b/backend/server.js
index 6e7d2dd..653d369 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -14,6 +14,7 @@ const userRoutes = require('./routes/userRoutes');
const gymClassRoutes = require('./routes/gymClassRoutes');
const bookingRoutes = require('./routes/bookingRoutes');
const uploadRoutes = require('./routes/uploadRoutes');
+const authRoutes = require('./routes/authRoutes');
// Initialize express app
const app = express();
@@ -41,6 +42,7 @@ app.use('/api/users', userRoutes);
app.use('/api/classes', gymClassRoutes);
app.use('/api/bookings', bookingRoutes);
app.use('/api/upload', uploadRoutes);
+app.use('/api/auth', authRoutes);
// Root route
app.get('/', (req, res) => {
diff --git a/cap1.md b/cap1.md
new file mode 100644
index 0000000..f5cd90c
--- /dev/null
+++ b/cap1.md
@@ -0,0 +1,453 @@
+# Taller: Desarrollo con Angular e Ionic
+
+## Capítulo 1: Introducción a Angular e Ionic
+
+## 1. ¿Qué es Angular?
+
+Angular es un framework de desarrollo front-end mantenido por Google, diseñado para crear aplicaciones web de una sola página (SPA) y aplicaciones móviles. Algunas características clave:
+
+- **Basado en componentes**: Toda la interfaz se construye mediante componentes reutilizables
+- **TypeScript**: Utiliza TypeScript como lenguaje principal, añadiendo tipos estáticos a JavaScript
+- **Completo**: Ofrece soluciones integradas para enrutamiento, formularios, HTTP, animaciones, etc.
+- **Modular**: Su arquitectura permite dividir la aplicación en módulos funcionales
+- **Reactivo**: Facilita la programación reactiva mediante RxJS
+
+Angular utiliza un sistema de "detección de cambios" para mantener sincronizada la interfaz de usuario con el estado de la aplicación, lo que permite crear interfaces dinámicas y reactivas.
+
+## 2. Angular CLI: La Herramienta de Línea de Comandos
+
+El Angular CLI (Command Line Interface) es una herramienta oficial que simplifica enormemente el desarrollo con Angular:
+
+```bash
+# Instalación global
+npm install -g @angular/cli
+
+# Crear nuevo proyecto
+ng new mi-proyecto
+
+# Generar componentes, servicios, etc.
+ng generate component mi-componente
+ng generate service mi-servicio
+
+# Iniciar servidor de desarrollo
+ng serve
+
+# Construir para producción
+ng build --prod
+```
+
+Beneficios del CLI:
+
+- **Scaffolding**: Generación de código con estructura y configuración correctas
+- **Herramientas de desarrollo**: Servidor local con recarga en vivo (live reload)
+- **Optimización**: Empaquetado y minificación para producción
+- **Testing**: Configuración automática para pruebas unitarias e integración
+- **Actualización**: Facilita la actualización entre versiones de Angular
+
+## 3. Angular vs Desarrollo Web Tradicional
+
+Angular se asemeja al desarrollo de páginas web normales, pero con una estructura y enfoque diferentes:
+
+| Desarrollo Web Tradicional | Desarrollo con Angular |
+|---------------------------|------------------------|
+| Páginas HTML separadas | Aplicación de una sola página (SPA) |
+| jQuery para manipular DOM | Vinculación de datos bidireccional |
+| JavaScript vanilla | TypeScript con tipos estáticos |
+| Recarga completa entre páginas | Navegación sin recarga (enrutamiento SPA) |
+| Mezcla de lógica y presentación | Separación modelo-vista-controlador |
+| Scripts y estilos globales | Encapsulación de componentes |
+
+A pesar de las diferencias, los conocimientos de HTML, CSS y JavaScript son totalmente aplicables en Angular, ya que seguimos trabajando con estos lenguajes fundamentales pero de manera estructurada.
+
+## 4. Componentes Básicos de Angular
+
+#### Componentes
+
+El elemento fundamental de las aplicaciones Angular:
+
+```typescript
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-contador',
+ template: `
+
+
Contador: {{ contador }}
+
+
+
+ `,
+ styles: [`
+ div { text-align: center; }
+ button { margin: 0 5px; }
+ `]
+})
+export class ContadorComponent {
+ contador = 0;
+
+ incrementar() {
+ this.contador++;
+ }
+
+ decrementar() {
+ this.contador--;
+ }
+}
+```
+
+#### Módulos
+
+Organizan la aplicación en bloques funcionales:
+
+```typescript
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { AppComponent } from './app.component';
+import { ContadorComponent } from './contador/contador.component';
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ContadorComponent
+ ],
+ imports: [
+ BrowserModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
+```
+
+#### Servicios
+
+Encapsulan la lógica de negocio y son inyectables en componentes:
+
+```typescript
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DatosService {
+ private datos = ['Primer elemento', 'Segundo elemento'];
+
+ getDatos() {
+ return this.datos;
+ }
+
+ agregarDato(dato: string) {
+ this.datos.push(dato);
+ }
+}
+```
+
+#### Directivas
+
+Modifican el comportamiento del DOM:
+
+```html
+
+
+
{{ item }}
+
+
+
+
+ Este contenido es condicional
+
+
+
+
+ Texto con estilo dinámico
+
+```
+
+#### Pipes
+
+Transforman datos para su visualización:
+
+```html
+
{{ fecha | date:'dd/MM/yyyy' }}
+
{{ precio | currency:'EUR' }}
+
{{ nombre | uppercase }}
+
{{ textoLargo | slice:0:50 }}...
+```
+
+## 5. ¿Qué es Ionic?
+
+Ionic es un framework de desarrollo de aplicaciones móviles híbridas que se construye sobre Angular:
+
+- **Multiplataforma**: Una única base de código para iOS, Android y web
+- **Componentes nativos**: UI con apariencia y comportamiento nativo
+- **Capacitor/Cordova**: Acceso a APIs nativas del dispositivo
+- **Rendimiento optimizado**: Aplicaciones rápidas y responsivas
+
+## 6. Componentes UI de Ionic
+
+Ionic ofrece una amplia biblioteca de componentes que siguen las directrices de diseño de iOS y Android:
+
+#### Navegación y Estructura
+
+```html
+
+
+
+
+
+ Inicio
+
+
+
+ Buscar
+
+
+
+ Perfil
+
+
+
+
+
+
+
+
+ Menú
+
+
+
+
+ Inicio
+ Perfil
+ Configuración
+
+
+
+```
+
+> **Nota:** Para ver ejemplos interactivos de estos componentes, visite la documentación oficial:
+> - [Ionic Tabs](https://ionicframework.com/docs/api/tabs)
+> - [Ionic Menu](https://ionicframework.com/docs/api/menu)
+
+#### Componentes Básicos
+
+```html
+
+Botón Estándar
+Botón Outline
+Botón Block
+
+
+
+
+ Subtítulo
+ Título Principal
+
+
+ Contenido detallado de la tarjeta que puede incluir
+ texto, imágenes y otros elementos.
+
+
+
+
+
+ Usuarios
+
+
+
+
+
+
Juan Pérez
+
Desarrollador
+
+
+
+
+
+
+
+
María García
+
Diseñadora
+
+
+
+```
+
+> **Nota:** Puede visualizar estos componentes en la documentación oficial:
+> - [Ionic Buttons](https://ionicframework.com/docs/api/button)
+> - [Ionic Cards](https://ionicframework.com/docs/api/card)
+> - [Ionic Lists](https://ionicframework.com/docs/api/list)
+
+#### Formularios y Entrada
+
+```html
+
+
+ Nombre
+
+
+
+
+ Email
+
+
+
+
+
+ Categoría
+
+ Deportes
+ Música
+ Tecnología
+
+
+
+
+
+ Notificaciones
+
+
+
+
+ Acepto términos
+
+
+```
+
+> **Nota:** Consulte la documentación oficial para ver ejemplos interactivos:
+> - [Ionic Input](https://ionicframework.com/docs/api/input)
+> - [Ionic Select](https://ionicframework.com/docs/api/select)
+> - [Ionic Toggle](https://ionicframework.com/docs/api/toggle)
+> - [Ionic Checkbox](https://ionicframework.com/docs/api/checkbox)
+
+#### Feedback y Alertas
+
+```typescript
+import { AlertController, ToastController, LoadingController } from '@ionic/angular';
+
+constructor(
+ private alertCtrl: AlertController,
+ private toastCtrl: ToastController,
+ private loadingCtrl: LoadingController
+) {}
+
+async mostrarAlerta() {
+ const alert = await this.alertCtrl.create({
+ header: 'Alerta',
+ subHeader: 'Información importante',
+ message: '¿Estás seguro de realizar esta acción?',
+ buttons: ['Cancelar', 'Aceptar']
+ });
+ await alert.present();
+}
+
+async mostrarToast() {
+ const toast = await this.toastCtrl.create({
+ message: 'Operación completada con éxito',
+ duration: 2000,
+ position: 'bottom',
+ color: 'success'
+ });
+ toast.present();
+}
+
+async mostrarCargando() {
+ const loading = await this.loadingCtrl.create({
+ message: 'Cargando datos...',
+ duration: 2000
+ });
+ await loading.present();
+}
+```
+
+> **Nota:** Para más información sobre alertas y elementos interactivos, consulte:
+> - [Ionic Alert](https://ionicframework.com/docs/api/alert)
+> - [Ionic Toast](https://ionicframework.com/docs/api/toast)
+> - [Ionic Loading](https://ionicframework.com/docs/api/loading)
+
+## 7. Capacitor: El Puente Nativo
+
+Capacitor es el framework que permite a Ionic acceder a las capacidades nativas del dispositivo:
+
+- Cámara y galería de fotos
+- Geolocalización
+- Almacenamiento persistente
+- Notificaciones push
+- Sensores del dispositivo
+- Archivos y sistema de archivos
+
+Ejemplo básico de uso de Capacitor:
+
+```typescript
+import { Camera, CameraResultType } from '@capacitor/camera';
+
+async function tomarFoto() {
+ const imagen = await Camera.getPhoto({
+ quality: 90,
+ allowEditing: true,
+ resultType: CameraResultType.Uri
+ });
+
+ // Usar la imagen (imagen.webPath)
+ const imagenUrl = imagen.webPath;
+}
+```
+
+## 8. Ciclo de Desarrollo con Ionic y Angular
+
+El proceso típico de desarrollo con Ionic y Angular incluye:
+
+1. **Creación del proyecto**:
+ ```bash
+ # Crear un nuevo proyecto (por defecto usa Angular)
+ ionic start mi-proyecto-ionic
+
+ # Especificar el framework (angular, react, vue)
+ ionic start mi-proyecto-ionic --type=angular
+ ```
+
+2. **Desarrollo en navegador web**:
+ ```bash
+ # Iniciar servidor de desarrollo con recarga en vivo
+ ionic serve
+ ```
+
+3. **Pruebas en dispositivo real**:
+ ```bash
+ # Añadir plataforma Android
+ ionic capacitor add android
+
+ # Ejecutar en dispositivo con recarga en vivo
+ ionic capacitor run android --livereload
+ ```
+
+4. **Compilación para producción**:
+ ```bash
+ # Construir la aplicación optimizada
+ ionic build --prod
+
+ # Copiar los archivos a las plataformas nativas
+ npx cap copy
+
+ # Abrir el proyecto en Android Studio para ajustes finales
+ npx cap open android
+ ```
+
+## Resumen
+
+Angular e Ionic forman una poderosa combinación para desarrollar aplicaciones móviles multiplataforma:
+
+- Angular proporciona la estructura, organización y lógica
+- Ionic aporta componentes UI con aspecto nativo
+- Capacitor permite acceder a características nativas del dispositivo
+- El desarrollo es similar al web tradicional, pero más estructurado y optimizado
+
+En los siguientes capítulos, exploraremos más a fondo cada uno de estos conceptos y construiremos paso a paso una aplicación completa de reservas para un gimnasio, además de la creación básica de un proyecto vacío.
+
+## Recursos y Documentación
+
+- [Documentación oficial de Angular](https://angular.io/docs)
+- [Documentación de Ionic Framework](https://ionicframework.com/docs)
+- [Capacitor Docs](https://capacitorjs.com/docs)
+- [Angular CLI](https://cli.angular.io/)
+- [TypeScript](https://www.typescriptlang.org/docs/)
diff --git a/cap2.md b/cap2.md
new file mode 100644
index 0000000..348bd1c
--- /dev/null
+++ b/cap2.md
@@ -0,0 +1,768 @@
+# Taller: Desarrollo con Angular e Ionic
+
+## Capítulo 2: Componentes y Estructura de una Aplicación Angular/Ionic
+
+## 1. Estructura de una Aplicación Angular/Ionic
+
+Al crear un nuevo proyecto de Ionic con Angular mediante el comando `ionic start`, se genera una estructura de carpetas organizada:
+
+```
+mi-app/
+├── src/ # Código fuente principal
+│ ├── app/ # Lógica y componentes de la aplicación
+│ │ ├── app.component.ts
+│ │ ├── app.module.ts
+│ │ ├── app-routing.module.ts
+│ ├── assets/ # Recursos estáticos (imágenes, fuentes, etc.)
+│ ├── environments/ # Configuraciones por entorno (dev, prod)
+│ ├── theme/ # Variables globales de estilo (colores, etc.)
+│ ├── global.scss # Estilos globales
+│ ├── index.html # Archivo HTML raíz
+│ ├── main.ts # Punto de entrada de la aplicación
+├── angular.json # Configuración de Angular
+├── capacitor.config.ts # Configuración de Capacitor
+├── package.json # Dependencias y scripts
+├── tsconfig.json # Configuración de TypeScript
+
+```
+
+### Archivos Clave:
+
+- **app.module.ts**: Módulo principal que configura la aplicación
+- **app-routing.module.ts**: Define las rutas de navegación
+- **app.component.ts**: Componente raíz que contiene toda la aplicación
+- **index.html**: Archivo HTML base donde se monta la aplicación
+- **main.ts**: Punto de entrada que arranca la aplicación Angular
+
+## 2. Fundamentos de los Componentes Angular
+
+### Anatomía de un Componente
+
+Cada componente Angular consta de:
+
+1. **Clase TypeScript**: Contiene la lógica y datos
+2. **Plantilla HTML**: Define la estructura visual
+3. **Estilos CSS**: Define la apariencia (opcional)
+4. **Metadatos**: Configuración mediante decorador @Component
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-producto', // Cómo se referencia en HTML
+ templateUrl: './producto.component.html', // Plantilla HTML
+ styleUrls: ['./producto.component.scss'] // Estilos
+})
+export class ProductoComponent implements OnInit {
+ nombre: string = 'Smartphone';
+ precio: number = 599.99;
+ disponible: boolean = true;
+
+ constructor() { }
+
+ ngOnInit() {
+ // Inicialización del componente
+ }
+
+ aplicarDescuento(porcentaje: number): void {
+ this.precio = this.precio * (1 - porcentaje/100);
+ }
+}
+
+```
+
+### Ciclo de Vida de los Componentes
+
+Los componentes tienen un ciclo de vida gestionado por Angular:
+
+1. **ngOnChanges**: Cuando cambian las propiedades de entrada (@Input)
+2. **ngOnInit**: Después de la primera inicialización
+3. **ngAfterViewInit**: Cuando la vista se ha inicializado
+4. **ngOnDestroy**: Justo antes de que Angular destruya el componente
+
+```typescript
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { DatosService } from './datos.service';
+
+@Component({
+ selector: 'app-ejemplo',
+ template: '
{{datos}}
'
+})
+export class EjemploComponent implements OnInit, OnDestroy {
+ datos: any[] = [];
+ private suscripcion!: Subscription;
+
+ constructor(private datosService: DatosService) { }
+
+ ngOnInit() {
+ // Perfecto para inicializar datos, suscripciones, etc.
+ this.suscripcion = this.datosService.obtenerDatos()
+ .subscribe(datos => {
+ this.datos = datos;
+ });
+ }
+
+ ngOnDestroy() {
+ // Limpieza antes de destruir el componente
+ if (this.suscripcion) {
+ this.suscripcion.unsubscribe();
+ }
+ }
+}
+
+```
+
+## 3. Vinculación de Datos (Data Binding)
+
+Angular ofrece cuatro formas de vinculación de datos:
+
+### Interpolación {{ }}
+
+Muestra valores de propiedades en la plantilla:
+
+```html
+
{{ titulo }}
+
Precio: {{ precio | currency }}
+
Estado: {{ disponible ? 'En stock' : 'Agotado' }}
+
+```
+
+### Property Binding [ ]
+
+Vincula propiedades HTML con valores del componente:
+
+```html
+
+
+
+ Contenido con clases dinámicas
+
+
+```
+
+### Event Binding ( )
+
+Responde a eventos del usuario:
+
+```html
+
+
+
+
+```
+
+### Two-Way Binding [( )]
+
+Combina property binding y event binding para actualizar datos en ambas direcciones:
+
+```html
+
+
+
+
+
+```
+
+> Nota: Para usar ngModel, debes importar FormsModule en tu módulo Angular.
+
+## 4. Directivas en Angular
+
+Las directivas son clases que extienden HTML con nueva funcionalidad.
+
+### Directivas Estructurales
+
+Modifican el DOM añadiendo o quitando elementos:
+
+```html
+
+
+
+```
+
+**pages/profile/profile.page.ts**:
+
+```typescript
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { AlertController, ToastController, NavController, ActionSheetController } from '@ionic/angular';
+import { Subscription } from 'rxjs';
+import { User } from '../../models/user.model';
+import { AuthService } from '../../services/auth.service';
+import { BookingsService } from '../../services/bookings.service';
+import { UploadService } from '../../services/upload.service';
+import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
+
+@Component({
+ selector: 'app-profile',
+ templateUrl: './profile.page.html',
+ styleUrls: ['./profile.page.scss'],
+})
+export class ProfilePage implements OnInit, OnDestroy {
+ usuario: User | null = null;
+ profileImage: string | undefined;
+ notificationsEnabled = true;
+ estadisticas = {
+ totalReservas: 0,
+ clasesCompletadas: 0
+ };
+ private subscription: Subscription = new Subscription();
+
+ constructor(
+ private authService: AuthService,
+ private bookingsService: BookingsService,
+ private uploadService: UploadService,
+ private alertController: AlertController,
+ private toastController: ToastController,
+ private navController: NavController,
+ private actionSheetController: ActionSheetController
+ ) { }
+
+ ngOnInit() {
+ this.cargarDatosUsuario();
+
+ // Suscribirse a cambios en el usuario
+ const userSub = this.authService.currentUser$.subscribe(user => {
+ if (user) {
+ this.usuario = user;
+ this.profileImage = user.profilePic || user.profilePicUrl;
+ this.notificationsEnabled = user.notificationsEnabled ||
+ (user.preferences ? user.preferences.notifications : true);
+ }
+ });
+
+ this.subscription.add(userSub);
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+
+ ionViewWillEnter() {
+ this.cargarDatosUsuario();
+ this.cargarEstadisticas();
+ }
+
+ cargarDatosUsuario() {
+ this.usuario = this.authService.getCurrentUser();
+ if (this.usuario) {
+ this.profileImage = this.usuario.profilePic || this.usuario.profilePicUrl;
+ this.notificationsEnabled = this.usuario.notificationsEnabled ||
+ (this.usuario.preferences ? this.usuario.preferences.notifications : true);
+ }
+ }
+
+ cargarEstadisticas() {
+ const sub = this.bookingsService.getUserBookings().subscribe(bookings => {
+ this.estadisticas.totalReservas = bookings.length;
+ // Contar reservas completadas (las que no están canceladas y son pasadas)
+ this.estadisticas.clasesCompletadas = bookings.filter(b =>
+ b.status === 'confirmed' && new Date(b.date) < new Date()
+ ).length;
+ });
+
+ this.subscription.add(sub);
+ }
+
+ async cambiarFotoPerfil() {
+ const actionSheet = await this.actionSheetController.create({
+ header: 'Cambiar foto de perfil',
+ buttons: [
+ {
+ text: 'Tomar foto',
+ icon: 'camera',
+ handler: () => {
+ this.tomarFoto(CameraSource.Camera);
+ }
+ },
+ {
+ text: 'Elegir de la galería',
+ icon: 'image',
+ handler: () => {
+ this.tomarFoto(CameraSource.Photos);
+ }
+ },
+ {
+ text: 'Cancelar',
+ icon: 'close',
+ role: 'cancel'
+ }
+ ]
+ });
+ await actionSheet.present();
+ }
+
+ async tomarFoto(source: CameraSource) {
+ try {
+ const permisos = await Camera.requestPermissions();
+
+ if (permisos.photos === 'granted' || permisos.camera === 'granted') {
+ const imagen = await Camera.getPhoto({
+ quality: 90,
+ allowEditing: true,
+ resultType: CameraResultType.Uri,
+ source: source
+ });
+
+ // Si tenemos la imagen, preparamos para subir
+ if (imagen.webPath && imagen.path) {
+ this.mostrarToast('Subiendo imagen...', 'primary');
+
+ // Convertir Uri a File
+ const blob = await fetch(imagen.webPath).then(r => r.blob());
+ const file = new File([blob], `profile-${Date.now()}.${imagen.format || 'jpeg'}`, {
+ type: `image/${imagen.format || 'jpeg'}`
+ });
+
+ // Subir la imagen al servidor
+ const sub = this.uploadService.uploadImage(file, 'avatar').subscribe({
+ next: (response) => {
+ if (response && response.imageUrl) {
+ // Actualizar perfil del usuario con la nueva imagen
+ if (this.usuario) {
+ this.authService.updateUserProfile({
+ ...this.usuario,
+ profilePicUrl: response.imageUrl
+ }).subscribe({
+ next: (updatedUser) => {
+ this.profileImage = updatedUser.profilePicUrl || updatedUser.profilePic;
+ this.mostrarToast('Foto de perfil actualizada', 'success');
+ },
+ error: (error) => {
+ console.error('Error al actualizar perfil:', error);
+ this.mostrarToast('Error al actualizar perfil', 'danger');
+ }
+ });
+ }
+ }
+ },
+ error: (error) => {
+ console.error('Error al subir imagen:', error);
+ this.mostrarToast('Error al subir la imagen', 'danger');
+ }
+ });
+
+ this.subscription.add(sub);
+ }
+ } else {
+ this.mostrarToast('Necesitamos permiso para acceder a la cámara/galería', 'warning');
+ }
+ } catch (error) {
+ console.error('Error al tomar foto', error);
+ this.mostrarToast('Error al procesar la imagen', 'danger');
+ }
+ }
+
+ async editarPerfil() {
+ const alert = await this.alertController.create({
+ header: 'Editar Perfil',
+ inputs: [
+ {
+ name: 'name',
+ type: 'text',
+ placeholder: 'Nombre',
+ value: this.usuario?.name
+ }
+ ],
+ buttons: [
+ {
+ text: 'Cancelar',
+ role: 'cancel'
+ },
+ {
+ text: 'Guardar',
+ handler: (data) => {
+ if (this.usuario && data.name && data.name.trim() !== '') {
+ this.authService.updateUserProfile({
+ ...this.usuario,
+ name: data.name.trim()
+ }).subscribe({
+ next: () => {
+ this.mostrarToast('Perfil actualizado correctamente', 'success');
+ },
+ error: (error) => {
+ console.error('Error al actualizar perfil:', error);
+ this.mostrarToast('Error al actualizar perfil', 'danger');
+ }
+ });
+ }
+ }
+ }
+ ]
+ });
+
+ await alert.present();
+ }
+
+ toggleNotifications() {
+ if (this.usuario) {
+ // Preparar datos para actualizar
+ const updatedUserData: Partial = {
+ ...this.usuario,
+ notificationsEnabled: this.notificationsEnabled,
+ preferences: {
+ ...this.usuario.preferences,
+ notifications: this.notificationsEnabled
+ }
+ };
+
+ // Actualizar en el backend
+ this.authService.updateUserProfile(updatedUserData).subscribe({
+ next: () => {
+ this.mostrarToast(
+ this.notificationsEnabled ? 'Notificaciones activadas' : 'Notificaciones desactivadas',
+ 'success'
+ );
+ },
+ error: (error) => {
+ console.error('Error al actualizar preferencias', error);
+ this.mostrarToast('Error al actualizar preferencias', 'danger');
+ // Revertir el toggle si hubo error
+ this.notificationsEnabled = !this.notificationsEnabled;
+ }
+ });
+ }
+ }
+
+ async mostrarAyuda() {
+ const alert = await this.alertController.create({
+ header: 'Ayuda y Soporte',
+ message: 'Para cualquier consulta o problema con la aplicación, contáctanos en: soporte@gymapp.com',
+ buttons: ['Entendido']
+ });
+ await alert.present();
+ }
+
+ async confirmarCerrarSesion() {
+ const alert = await this.alertController.create({
+ header: 'Cerrar Sesión',
+ message: '¿Estás seguro de que deseas cerrar sesión?',
+ buttons: [
+ {
+ text: 'Cancelar',
+ role: 'cancel'
+ },
+ {
+ text: 'Cerrar Sesión',
+ handler: () => {
+ this.cerrarSesion();
+ }
+ }
+ ]
+ });
+ await alert.present();
+ }
+
+ async cerrarSesion() {
+ try {
+ await this.authService.logout();
+ this.mostrarToast('Sesión cerrada', 'success');
+ this.navController.navigateRoot('/auth');
+ } catch (error) {
+ console.error('Error al cerrar sesión', error);
+ this.mostrarToast('Error al cerrar sesión', 'danger');
+ }
+ }
+
+ async mostrarToast(mensaje: string, color: string = 'primary') {
+ const toast = await this.toastController.create({
+ message: mensaje,
+ duration: 2000,
+ position: 'bottom',
+ color: color
+ });
+ toast.present();
+ }
+}
+```
+
+## 6. Implementación de Notificaciones Locales
+
+Para implementar notificaciones locales, primero instalamos el plugin de Capacitor:
+
+```bash
+npm install @capacitor/local-notifications
+npx cap sync
+```
+
+Ahora implementamos el servicio de notificaciones:
+
+**services/notification.service.ts**:
+
+```typescript
+import { Injectable } from '@angular/core';
+import { LocalNotifications } from '@capacitor/local-notifications';
+import { Booking } from '../models/booking.model';
+import { GymClass } from '../models/gym-class.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NotificationService {
+
+ constructor() {
+ this.initialize();
+ }
+
+ private async initialize() {
+ try {
+ // Solicitar permisos al inicializar
+ const permResult = await LocalNotifications.requestPermissions();
+ console.log('Permisos de notificación:', permResult.display);
+ } catch (error) {
+ console.error('Error al inicializar notificaciones', error);
+ }
+ }
+
+ async scheduleClassReminder(booking: Booking, gymClass: GymClass) {
+ try {
+ // Verificar que tenemos permisos
+ const permResult = await LocalNotifications.requestPermissions();
+
+ if (permResult.display !== 'granted') {
+ console.log('Permisos de notificación no concedidos');
+ return;
+ }
+
+ // Configurar el tiempo de la notificación (1 hora antes de la clase)
+ const classTime = new Date(gymClass.startTime);
+ const notificationTime = new Date(classTime);
+ notificationTime.setHours(notificationTime.getHours() - 1);
+
+ // No programar notificaciones en el pasado
+ if (notificationTime <= new Date()) {
+ console.log('No se programó notificación (fecha en el pasado)');
+ return;
+ }
+
+ // Crear notificación
+ await LocalNotifications.schedule({
+ notifications: [
+ {
+ id: parseInt(booking.id.replace(/\D/g, '').substr(0, 8)) || Math.floor(Math.random() * 100000),
+ title: `¡Recordatorio de clase: ${gymClass.name}!`,
+ body: `Tu clase de ${gymClass.name} con ${gymClass.instructor} comienza en 1 hora.`,
+ schedule: { at: notificationTime },
+ sound: 'default',
+ actionTypeId: '',
+ extra: {
+ bookingId: booking.id,
+ classId: gymClass.id
+ }
+ }
+ ]
+ });
+
+ console.log(`Notificación programada para ${notificationTime.toLocaleString()}`);
+
+ } catch (error) {
+ console.error('Error al programar notificación', error);
+ }
+ }
+
+ async cancelNotification(bookingId: string) {
+ try {
+ // Obtener notificaciones pendientes
+ const pendingList = await LocalNotifications.getPending();
+
+ // Buscar la notificación asociada a esta reserva
+ const notificationToCancel = pendingList.notifications.find(notification =>
+ notification.extra && notification.extra.bookingId === bookingId
+ );
+
+ if (notificationToCancel) {
+ // Cancelar la notificación
+ await LocalNotifications.cancel({
+ notifications: [
+ { id: notificationToCancel.id }
+ ]
+ });
+
+ console.log(`Notificación para reserva ${bookingId} cancelada`);
+ }
+ } catch (error) {
+ console.error('Error al cancelar notificación', error);
+ }
+ }
+
+ async checkNotificationStatus() {
+ try {
+ // Verificar el estado de los permisos
+ const permResult = await LocalNotifications.checkPermissions();
+ return permResult.display === 'granted';
+ } catch (error) {
+ console.error('Error al verificar permisos', error);
+ return false;
+ }
+ }
+}
+```
+
+## 7. Configuración Final para Deployment
+
+### Ajustes del archivo capacitor.config.ts
+
+Configuremos los ajustes de Capacitor para nuestra aplicación:
+
+```typescript
+import { CapacitorConfig } from '@capacitor/cli';
+
+const config: CapacitorConfig = {
+ appId: 'com.example.gymreservation',
+ appName: 'Gym Reservation',
+ webDir: 'www',
+ server: {
+ androidScheme: 'https'
+ },
+ plugins: {
+ LocalNotifications: {
+ smallIcon: "ic_stat_icon_config_sample",
+ iconColor: "#488AFF",
+ sound: "beep.wav"
+ },
+ Camera: {
+ permissions: ['camera', 'photos']
+ }
+ }
+};
+
+export default config;
+```
+
+### Compilación para Producción
+
+```bash
+# Compilar para producción
+ionic build --prod
+
+# Sincronizar con proyectos nativos
+npx cap sync
+
+# Abrir proyecto en Android Studio
+npx cap open android
+```
+
+## 8. Integración con Plataformas Nativas
+
+### Permisos en Android
+
+Para usar la cámara y las notificaciones en Android, necesitamos añadir permisos al archivo `AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+## Resumen
+
+En este capítulo, hemos completado nuestra aplicación de reservas para gimnasio integrándola con un backend real:
+
+1. Implementamos servicios que interactúan con un backend Node.js
+2. Creamos una interfaz de usuario completa para la gestión de clases y reservas
+3. Añadimos autenticación de usuarios con registro y login
+4. Implementamos la subida de imágenes para perfiles de usuario
+5. Configuramos notificaciones locales para recordatorios de clases
+6. Preparamos la aplicación para su despliegue en dispositivos móviles
+
+La aplicación ahora cuenta con todas las funcionalidades principales planificadas y está lista para ser utilizada en un entorno real. Los usuarios pueden registrarse, explorar clases disponibles, realizar reservas, gestionar su perfil y recibir notificaciones sobre sus próximas clases.
+
+## Próximos Pasos
+
+En el siguiente capítulo, exploraremos:
+
+1. Pruebas y depuración en dispositivos reales
+2. Optimizaciones de rendimiento
+3. Consideraciones para la publicación en tiendas de aplicaciones
+4. Posibles extensiones y funcionalidades adicionales
\ No newline at end of file
diff --git a/cap6.md b/cap6.md
new file mode 100644
index 0000000..ffc972e
--- /dev/null
+++ b/cap6.md
@@ -0,0 +1,711 @@
+# Capítulo 6: Estructura del Backend y Despliegue de la Aplicación
+
+En este capítulo, exploraremos la estructura del backend que hemos implementado para nuestra aplicación de reservas de gimnasio y el proceso de despliegue para dispositivos móviles reales.
+
+## 1. Arquitectura del Backend
+
+Nuestra aplicación ahora cuenta con un backend real basado en Node.js, Express y PostgreSQL que gestiona todas las operaciones de datos. Este enfoque tiene varias ventajas sobre el almacenamiento local:
+
+- Datos centralizados y accesibles desde cualquier dispositivo
+- Mayor seguridad y control de acceso
+- Escalabilidad mejorada para manejar más usuarios
+- Sincronización de datos en tiempo real
+
+### Estructura de Carpetas del Backend
+
+```
+backend/
+├── config/
+│ └── db.js # Configuración de la base de datos
+├── controllers/
+│ ├── authController.js # Controlador de autenticación
+│ ├── bookingController.js # Controlador de reservas
+│ ├── gymClassController.js # Controlador de clases
+│ ├── uploadController.js # Controlador para subida de archivos
+│ └── userController.js # Controlador de usuarios
+├── models/
+│ ├── Booking.js # Modelo de datos para reservas
+│ ├── GymClass.js # Modelo de datos para clases
+│ ├── User.js # Modelo de datos para usuarios
+│ └── index.js # Exporta todos los modelos
+├── routes/
+│ ├── authRoutes.js # Rutas de autenticación
+│ ├── bookingRoutes.js # Rutas de reservas
+│ ├── gymClassRoutes.js # Rutas de clases
+│ ├── uploadRoutes.js # Rutas para subida de archivos
+│ └── userRoutes.js # Rutas de usuarios
+├── uploads/ # Directorio para archivos subidos
+├── server.js # Punto de entrada principal
+└── package.json # Dependencias y scripts
+```
+
+### Tecnologías Utilizadas en el Backend
+
+- **Node.js**: Entorno de ejecución JavaScript del lado del servidor
+- **Express**: Framework web para crear APIs REST
+- **PostgreSQL**: Base de datos relacional robusta para almacenar los datos
+- **Sequelize**: ORM para modelar los datos y conectar con PostgreSQL
+- **JWT (JSON Web Tokens)**: Para gestionar la autenticación de usuarios
+- **Multer**: Para manejar la subida de archivos
+
+## 2. Implementación del Backend
+
+### Configuración de la Base de Datos
+
+**config/db.js**:
+
+```javascript
+const { Sequelize } = require('sequelize');
+const dotenv = require('dotenv');
+
+dotenv.config();
+
+// Create Sequelize instance
+let sequelize;
+
+if (process.env.DATABASE_URL) {
+ // Use connection string if available
+ sequelize = new Sequelize(process.env.DATABASE_URL, {
+ logging: process.env.NODE_ENV === 'development' ? console.log : false,
+ define: {
+ freezeTableName: true // Prevent Sequelize from pluralizing table names
+ },
+ dialectOptions: {
+ ssl: {
+ require: true,
+ rejectUnauthorized: false
+ }
+ },
+ pool: {
+ max: 5,
+ min: 0,
+ acquire: 30000,
+ idle: 10000
+ }
+ });
+} else {
+ // Fall back to individual parameters
+ sequelize = new Sequelize(
+ process.env.DB_NAME || 'postgres',
+ process.env.DB_USER || 'postgres',
+ process.env.DB_PASSWORD || 'postgres',
+ {
+ host: process.env.DB_HOST || 'localhost',
+ port: process.env.DB_PORT || 5432,
+ dialect: 'postgres',
+ logging: process.env.NODE_ENV === 'development' ? console.log : false,
+ define: {
+ freezeTableName: true // Prevent Sequelize from pluralizing table names
+ },
+ dialectOptions: {
+ ssl: process.env.NODE_ENV === 'production' ? {
+ require: true,
+ rejectUnauthorized: false
+ } : false
+ },
+ pool: {
+ max: 5,
+ min: 0,
+ acquire: 30000,
+ idle: 10000
+ }
+ }
+ );
+}
+
+// Test the connection
+const connectDB = async () => {
+ try {
+ await sequelize.authenticate();
+ console.log('Database connection established successfully.');
+ } catch (error) {
+ console.error('Unable to connect to the database:', error);
+ process.exit(1);
+ }
+};
+
+module.exports = { sequelize, connectDB };
+```
+
+### Modelos de Datos
+
+**models/User.js**:
+
+```javascript
+const { DataTypes } = require('sequelize');
+const { sequelize } = require('../config/db');
+const bcrypt = require('bcryptjs');
+
+const User = sequelize.define('user', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true
+ },
+ name: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ email: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ unique: true,
+ validate: {
+ isEmail: true
+ }
+ },
+ password: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ profilePicUrl: {
+ type: DataTypes.STRING,
+ defaultValue: '/uploads/default-avatar.jpg'
+ },
+ notificationsEnabled: {
+ type: DataTypes.BOOLEAN,
+ defaultValue: true
+ }
+}, {
+ hooks: {
+ beforeCreate: async (user) => {
+ if (user.password) {
+ const salt = await bcrypt.genSalt(10);
+ user.password = await bcrypt.hash(user.password, salt);
+ }
+ },
+ beforeUpdate: async (user) => {
+ if (user.changed('password')) {
+ const salt = await bcrypt.genSalt(10);
+ user.password = await bcrypt.hash(user.password, salt);
+ }
+ }
+ },
+ tableName: 'users' // Explicitly set lowercase table name
+});
+
+// Method to check if password matches
+User.prototype.matchPassword = async function(enteredPassword) {
+ return await bcrypt.compare(enteredPassword, this.password);
+};
+
+module.exports = User;
+```
+
+**models/GymClass.js**:
+
+```javascript
+const { DataTypes } = require('sequelize');
+const { sequelize } = require('../config/db');
+
+const GymClass = sequelize.define('gymClass', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true
+ },
+ name: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ description: {
+ type: DataTypes.TEXT,
+ allowNull: false
+ },
+ instructor: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ startTime: {
+ type: DataTypes.DATE,
+ allowNull: false
+ },
+ endTime: {
+ type: DataTypes.DATE,
+ allowNull: false
+ },
+ maxCapacity: {
+ type: DataTypes.INTEGER,
+ allowNull: false
+ },
+ currentBookings: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0
+ },
+ category: {
+ type: DataTypes.STRING,
+ defaultValue: 'Otros'
+ },
+ imageUrl: {
+ type: DataTypes.STRING,
+ defaultValue: '/uploads/default-class.jpg'
+ }
+}, {
+ tableName: 'classes' // Explicitly set lowercase table name
+});
+
+module.exports = GymClass;
+```
+
+**models/Booking.js**:
+
+```javascript
+const { DataTypes } = require('sequelize');
+const { sequelize } = require('../config/db');
+
+const Booking = sequelize.define('booking', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true
+ },
+ userId: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ references: {
+ model: 'users',
+ key: 'id'
+ }
+ },
+ classId: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ references: {
+ model: 'classes',
+ key: 'id'
+ }
+ },
+ className: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ date: {
+ type: DataTypes.DATE,
+ defaultValue: DataTypes.NOW
+ },
+ status: {
+ type: DataTypes.ENUM('confirmed', 'cancelled', 'pending'),
+ defaultValue: 'confirmed'
+ }
+}, {
+ tableName: 'bookings' // Explicitly set lowercase table name
+});
+
+module.exports = Booking;
+```
+
+**models/index.js**:
+
+```javascript
+const User = require('./User');
+const GymClass = require('./GymClass');
+const Booking = require('./Booking');
+
+// Define associations
+User.hasMany(Booking, { foreignKey: 'userId' });
+Booking.belongsTo(User, { foreignKey: 'userId' });
+
+GymClass.hasMany(Booking, { foreignKey: 'classId' });
+Booking.belongsTo(GymClass, { foreignKey: 'classId' });
+
+module.exports = {
+ User,
+ GymClass,
+ Booking
+};
+```
+
+### Controladores
+
+**controllers/authController.js**:
+
+```javascript
+const jwt = require('jsonwebtoken');
+const { User } = require('../models');
+
+// Generate JWT Token
+const generateToken = (id) => {
+ return jwt.sign({ id }, process.env.JWT_SECRET || 'secret_key_12345', {
+ expiresIn: '30d'
+ });
+};
+
+// @desc Register a new user
+// @route POST /api/auth/register
+// @access Public
+exports.registerUser = async (req, res) => {
+ try {
+ const { name, email, password } = req.body;
+
+ // Check if user already exists
+ const existingUser = await User.findOne({ where: { email } });
+ if (existingUser) {
+ return res.status(400).json({
+ success: false,
+ message: 'Email is already registered'
+ });
+ }
+
+ // Create user
+ const user = await User.create({
+ name,
+ email,
+ password
+ });
+
+ // Generate token
+ const token = generateToken(user.id);
+
+ res.status(201).json({
+ success: true,
+ token,
+ user: {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ profilePicUrl: user.profilePicUrl,
+ notificationsEnabled: user.notificationsEnabled
+ }
+ });
+ } catch (error) {
+ console.error('Error registering user:', error);
+ res.status(500).json({
+ success: false,
+ message: 'Server error',
+ error: error.message
+ });
+ }
+};
+
+// @desc Login user
+// @route POST /api/auth/login
+// @access Public
+exports.loginUser = async (req, res) => {
+ try {
+ const { email, password } = req.body;
+
+ // Check for user email
+ const user = await User.findOne({ where: { email } });
+ if (!user) {
+ return res.status(401).json({
+ success: false,
+ message: 'Invalid credentials'
+ });
+ }
+
+ // Check if password matches
+ const isMatch = await user.matchPassword(password);
+ if (!isMatch) {
+ return res.status(401).json({
+ success: false,
+ message: 'Invalid credentials'
+ });
+ }
+
+ // Generate token
+ const token = generateToken(user.id);
+
+ res.json({
+ success: true,
+ token,
+ user: {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ profilePicUrl: user.profilePicUrl,
+ notificationsEnabled: user.notificationsEnabled
+ }
+ });
+ } catch (error) {
+ console.error('Error logging in:', error);
+ res.status(500).json({
+ success: false,
+ message: 'Server error',
+ error: error.message
+ });
+ }
+};
+```
+
+### Rutas API
+
+**routes/authRoutes.js**:
+
+```javascript
+const express = require('express');
+const { registerUser, loginUser } = require('../controllers/authController');
+
+const router = express.Router();
+
+router.post('/register', registerUser);
+router.post('/login', loginUser);
+
+module.exports = router;
+```
+
+### Punto de Entrada del Servidor
+
+**server.js**:
+
+```javascript
+const path = require('path');
+const express = require('express');
+const dotenv = require('dotenv');
+const cors = require('cors');
+const morgan = require('morgan');
+const fs = require('fs');
+const { connectDB, sequelize } = require('./config/db');
+
+// Load env variables
+dotenv.config();
+
+// Import route files
+const userRoutes = require('./routes/userRoutes');
+const gymClassRoutes = require('./routes/gymClassRoutes');
+const bookingRoutes = require('./routes/bookingRoutes');
+const uploadRoutes = require('./routes/uploadRoutes');
+const authRoutes = require('./routes/authRoutes');
+
+// Initialize express app
+const app = express();
+
+// Middleware
+app.use(express.json());
+app.use(cors());
+
+// Dev logging middleware
+if (process.env.NODE_ENV === 'development') {
+ app.use(morgan('dev'));
+}
+
+// Create uploads directory if it doesn't exist
+const uploadsDir = path.join(__dirname, 'uploads');
+if (!fs.existsSync(uploadsDir)) {
+ fs.mkdirSync(uploadsDir, { recursive: true });
+}
+
+// Set static folder for uploads
+app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
+
+// Mount routers
+app.use('/api/users', userRoutes);
+app.use('/api/classes', gymClassRoutes);
+app.use('/api/bookings', bookingRoutes);
+app.use('/api/upload', uploadRoutes);
+app.use('/api/auth', authRoutes);
+
+// Root route
+app.get('/', (req, res) => {
+ res.json({ message: 'Welcome to Gym API' });
+});
+
+// Error handling middleware
+app.use((err, req, res, next) => {
+ console.error(err.stack);
+ res.status(500).json({
+ message: 'Server Error',
+ error: err.message
+ });
+});
+
+// Define port
+const PORT = process.env.PORT || 3000;
+
+// Connect to database and start server
+const startServer = async () => {
+ try {
+ // Connect to database
+ await connectDB();
+
+ await sequelize.sync();
+ console.log('Database synchronized');
+
+ // Start server
+ app.listen(PORT, () => {
+ console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
+ });
+ } catch (error) {
+ console.error('Unable to start server:', error);
+ process.exit(1);
+ }
+};
+
+startServer();
+
+// Handle unhandled promise rejections
+process.on('unhandledRejection', (err) => {
+ console.log(`Error: ${err.message}`);
+ // Close server & exit process
+ process.exit(1);
+});
+```
+
+## 3. Despliegue en Plataformas Móviles
+
+Una de las grandes ventajas de usar Ionic con Capacitor es la posibilidad de desplegar la misma base de código en múltiples plataformas. Vamos a revisar el proceso para Android.
+
+### Preparación para Android
+
+1. **Instalar las Dependencias de Android**:
+ - Android Studio
+ - JDK (Java Development Kit)
+ - Configurar variables de entorno (ANDROID_HOME, JAVA_HOME)
+
+2. **Añadir la Plataforma Android a nuestro Proyecto**:
+
+```bash
+# Instalar @capacitor/android si no está instalado
+npm install @capacitor/android
+
+# Añadir Android al proyecto
+npx cap add android
+```
+
+3. **Sincronizar el Proyecto**:
+
+Después de hacer cambios en el código web, necesitamos construir el proyecto y sincronizarlo con las plataformas nativas:
+
+```bash
+# Construir la aplicación
+ionic build --prod
+
+# Sincronizar con Android
+npx cap sync android
+```
+
+4. **Abrir el Proyecto en Android Studio**:
+
+```bash
+npx cap open android
+```
+
+### Configuración del AndroidManifest.xml
+
+El archivo `AndroidManifest.xml` es crucial para definir los permisos y configuraciones de la aplicación Android. Ya cubrimos algunos permisos esenciales:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+### Personalización de Iconos y Splash Screen
+
+Para personalizar los iconos y la pantalla de inicio:
+
+1. **Reemplazar los Archivos de Iconos**:
+ - Crear iconos para diferentes resoluciones
+ - Reemplazar los archivos en `android/app/src/main/res/mipmap-*/`
+
+2. **Reemplazar la Pantalla de Inicio (Splash Screen)**:
+ - Crear imágenes para diferentes orientaciones y resoluciones
+ - Reemplazar los archivos en `android/app/src/main/res/drawable-*/splash.png`
+
+3. **Personalizar el Nombre de la Aplicación**:
+ - Editar `android/app/src/main/res/values/strings.xml`:
+ ```xml
+
+ Gym Reservation
+ Gym Reservation
+ com.example.gymreservation
+ com.example.gymreservation
+
+ ```
+
+### Construir un APK de Prueba
+
+Para generar un APK que puedas compartir con probadores:
+
+1. En Android Studio, selecciona **Build > Build Bundle(s) / APK(s) > Build APK(s)**
+2. Cuando termine, haz clic en **locate** para encontrar el archivo APK
+3. Este archivo se puede compartir directamente para instalarlo en dispositivos Android (se debe habilitar la instalación desde fuentes desconocidas)
+
+### Construir un Bundle para Google Play
+
+Para publicar en la tienda de Google Play:
+
+1. En Android Studio, selecciona **Build > Generate Signed Bundle / APK**
+2. Elige **Android App Bundle**
+3. Crea o selecciona una clave de firma (keystore)
+4. Completa la información necesaria y genera el bundle (.aab)
+5. Este archivo es el que se sube a la Google Play Console
+
+## 4. Consideraciones de Seguridad
+
+Al desplegar una aplicación móvil que se comunica con un backend, es crucial considerar aspectos de seguridad:
+
+### Autenticación Segura
+
+Nuestra aplicación implementa JWT (JSON Web Tokens) para autenticar usuarios, pero aquí hay algunas mejoras adicionales:
+
+- **Tokens de Actualización**: Implementar tokens de corta duración con tokens de actualización
+- **Almacenamiento Seguro**: Utilizar almacenamiento nativo seguro para tokens
+- **HTTPS**: Garantizar que todas las comunicaciones API usen HTTPS
+
+### Manejo de Datos Sensibles
+
+- **No almacenar contraseñas en texto plano**: Ya estamos usando bcrypt para hash de contraseñas
+- **Minimizar los datos almacenados localmente**: Solo guardar lo necesario en el dispositivo
+- **Eliminar datos al cerrar sesión**: Limpiar datos sensibles cuando el usuario cierre sesión
+
+### Permisos de Aplicación
+
+- **Solicitar solo los permisos necesarios**: No solicitar permisos que no sean esenciales
+- **Explicar por qué se necesitan permisos**: Proporcionar contexto cuando se solicitan permisos
+- **Manejar denegaciones graciosamente**: La aplicación debe funcionar aunque se denieguen permisos no críticos
+
+## 5. Optimizaciones de Rendimiento
+
+Para garantizar que nuestra aplicación funcione de manera óptima en dispositivos reales:
+
+### Optimización de Imágenes
+
+- **Redimensionar imágenes**: Utilizar tamaños apropiados para dispositivos móviles
+- **Compresión de imágenes**: Reducir el tamaño de archivo sin perder calidad visible
+- **Carga progresiva**: Implementar carga progresiva para imágenes grandes
+
+### Optimización de Red
+
+- **Implementar caché**: Almacenar en caché respuestas de API para reducir solicitudes de red
+- **Carga perezosa (lazy loading)**: Cargar datos solo cuando sean necesarios
+- **Compresión de datos**: Utilizar gzip en el servidor para respuestas API
+
+### Optimización de la UI
+
+- **Virtualización de listas**: Para listas largas, solo renderizar elementos visibles
+- **Animaciones eficientes**: Usar animaciones CSS en lugar de JavaScript cuando sea posible
+- **Evitar work blocking**: No bloquear el hilo principal con operaciones pesadas
+
+## 6. Pruebas y Depuración
+
+Para identificar y resolver problemas en dispositivos reales:
+
+### Herramientas de Depuración
+
+- **Chrome Remote Debugging**: Para depurar WebView en dispositivos Android
+- **Safari Web Inspector**: Para depurar WebView en dispositivos iOS
+- **Capacitor Logs**: Usar `npx cap logs android` para ver logs en tiempo real
+
+### Pruebas en Dispositivos Reales
+
+- **Probar en diferentes tamaños de pantalla**: Asegurar que la UI sea responsiva
+- **Probar con diferentes velocidades de red**: Simular conexiones lentas
+- **Pruebas de batería**: Verificar que la aplicación no consuma demasiada batería
+
+## Resumen
+
+En este capítulo, hemos explorado:
+
+1. La arquitectura de nuestro backend Node.js/Express/PostgreSQL
+2. Los detalles de implementación de los modelos, controladores y rutas del backend
+3. El proceso de despliegue para dispositivos Android
+4. Consideraciones de seguridad para aplicaciones móviles
+5. Técnicas de optimización de rendimiento
+6. Herramientas y enfoques para pruebas y depuración
+
+Con esto, nuestra aplicación Gym Reservation está lista para ser utilizada por usuarios reales, con un backend robusto y una experiencia móvil optimizada.
\ No newline at end of file
diff --git a/combinado.md b/combinado.md
new file mode 100644
index 0000000..237d043
--- /dev/null
+++ b/combinado.md
@@ -0,0 +1,4663 @@
+
+# Taller: Desarrollo con Angular e Ionic
+
+## Capítulo 1: Introducción a Angular e Ionic
+
+## 1. ¿Qué es Angular?
+
+Angular es un framework de desarrollo front-end mantenido por Google, diseñado para crear aplicaciones web de una sola página (SPA) y aplicaciones móviles. Algunas características clave:
+
+- **Basado en componentes**: Toda la interfaz se construye mediante componentes reutilizables
+- **TypeScript**: Utiliza TypeScript como lenguaje principal, añadiendo tipos estáticos a JavaScript
+- **Completo**: Ofrece soluciones integradas para enrutamiento, formularios, HTTP, animaciones, etc.
+- **Modular**: Su arquitectura permite dividir la aplicación en módulos funcionales
+- **Reactivo**: Facilita la programación reactiva mediante RxJS
+
+Angular utiliza un sistema de "detección de cambios" para mantener sincronizada la interfaz de usuario con el estado de la aplicación, lo que permite crear interfaces dinámicas y reactivas.
+
+## 2. Angular CLI: La Herramienta de Línea de Comandos
+
+El Angular CLI (Command Line Interface) es una herramienta oficial que simplifica enormemente el desarrollo con Angular:
+
+```bash
+# Instalación global
+npm install -g @angular/cli
+
+# Crear nuevo proyecto
+ng new mi-proyecto
+
+# Generar componentes, servicios, etc.
+ng generate component mi-componente
+ng generate service mi-servicio
+
+# Iniciar servidor de desarrollo
+ng serve
+
+# Construir para producción
+ng build --prod
+```
+
+Beneficios del CLI:
+
+- **Scaffolding**: Generación de código con estructura y configuración correctas
+- **Herramientas de desarrollo**: Servidor local con recarga en vivo (live reload)
+- **Optimización**: Empaquetado y minificación para producción
+- **Testing**: Configuración automática para pruebas unitarias e integración
+- **Actualización**: Facilita la actualización entre versiones de Angular
+
+## 3. Angular vs Desarrollo Web Tradicional
+
+Angular se asemeja al desarrollo de páginas web normales, pero con una estructura y enfoque diferentes:
+
+| Desarrollo Web Tradicional | Desarrollo con Angular |
+|---------------------------|------------------------|
+| Páginas HTML separadas | Aplicación de una sola página (SPA) |
+| jQuery para manipular DOM | Vinculación de datos bidireccional |
+| JavaScript vanilla | TypeScript con tipos estáticos |
+| Recarga completa entre páginas | Navegación sin recarga (enrutamiento SPA) |
+| Mezcla de lógica y presentación | Separación modelo-vista-controlador |
+| Scripts y estilos globales | Encapsulación de componentes |
+
+A pesar de las diferencias, los conocimientos de HTML, CSS y JavaScript son totalmente aplicables en Angular, ya que seguimos trabajando con estos lenguajes fundamentales pero de manera estructurada.
+
+## 4. Componentes Básicos de Angular
+
+#### Componentes
+
+El elemento fundamental de las aplicaciones Angular:
+
+```typescript
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-contador',
+ template: `
+
+
Contador: {{ contador }}
+
+
+
+ `,
+ styles: [`
+ div { text-align: center; }
+ button { margin: 0 5px; }
+ `]
+})
+export class ContadorComponent {
+ contador = 0;
+
+ incrementar() {
+ this.contador++;
+ }
+
+ decrementar() {
+ this.contador--;
+ }
+}
+```
+
+#### Módulos
+
+Organizan la aplicación en bloques funcionales:
+
+```typescript
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { AppComponent } from './app.component';
+import { ContadorComponent } from './contador/contador.component';
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ContadorComponent
+ ],
+ imports: [
+ BrowserModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
+```
+
+#### Servicios
+
+Encapsulan la lógica de negocio y son inyectables en componentes:
+
+```typescript
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DatosService {
+ private datos = ['Primer elemento', 'Segundo elemento'];
+
+ getDatos() {
+ return this.datos;
+ }
+
+ agregarDato(dato: string) {
+ this.datos.push(dato);
+ }
+}
+```
+
+#### Directivas
+
+Modifican el comportamiento del DOM:
+
+```html
+
+
+
{{ item }}
+
+
+
+
+ Este contenido es condicional
+
+
+
+
+ Texto con estilo dinámico
+
+```
+
+#### Pipes
+
+Transforman datos para su visualización:
+
+```html
+
{{ fecha | date:'dd/MM/yyyy' }}
+
{{ precio | currency:'EUR' }}
+
{{ nombre | uppercase }}
+
{{ textoLargo | slice:0:50 }}...
+```
+
+## 5. ¿Qué es Ionic?
+
+Ionic es un framework de desarrollo de aplicaciones móviles híbridas que se construye sobre Angular:
+
+- **Multiplataforma**: Una única base de código para iOS, Android y web
+- **Componentes nativos**: UI con apariencia y comportamiento nativo
+- **Capacitor/Cordova**: Acceso a APIs nativas del dispositivo
+- **Rendimiento optimizado**: Aplicaciones rápidas y responsivas
+
+## 6. Componentes UI de Ionic
+
+Ionic ofrece una amplia biblioteca de componentes que siguen las directrices de diseño de iOS y Android:
+
+#### Navegación y Estructura
+
+```html
+
+
+
+
+
+ Inicio
+
+
+
+ Buscar
+
+
+
+ Perfil
+
+
+
+
+
+
+
+
+ Menú
+
+
+
+
+ Inicio
+ Perfil
+ Configuración
+
+
+
+```
+
+> **Nota:** Para ver ejemplos interactivos de estos componentes, visite la documentación oficial:
+> - [Ionic Tabs](https://ionicframework.com/docs/api/tabs)
+> - [Ionic Menu](https://ionicframework.com/docs/api/menu)
+
+#### Componentes Básicos
+
+```html
+
+Botón Estándar
+Botón Outline
+Botón Block
+
+
+
+
+ Subtítulo
+ Título Principal
+
+
+ Contenido detallado de la tarjeta que puede incluir
+ texto, imágenes y otros elementos.
+
+
+
+
+
+ Usuarios
+
+
+
+
+
+
Juan Pérez
+
Desarrollador
+
+
+
+
+
+
+
+
María García
+
Diseñadora
+
+
+
+```
+
+> **Nota:** Puede visualizar estos componentes en la documentación oficial:
+> - [Ionic Buttons](https://ionicframework.com/docs/api/button)
+> - [Ionic Cards](https://ionicframework.com/docs/api/card)
+> - [Ionic Lists](https://ionicframework.com/docs/api/list)
+
+#### Formularios y Entrada
+
+```html
+
+
+ Nombre
+
+
+
+
+ Email
+
+
+
+
+
+ Categoría
+
+ Deportes
+ Música
+ Tecnología
+
+
+
+
+
+ Notificaciones
+
+
+
+
+ Acepto términos
+
+
+```
+
+> **Nota:** Consulte la documentación oficial para ver ejemplos interactivos:
+> - [Ionic Input](https://ionicframework.com/docs/api/input)
+> - [Ionic Select](https://ionicframework.com/docs/api/select)
+> - [Ionic Toggle](https://ionicframework.com/docs/api/toggle)
+> - [Ionic Checkbox](https://ionicframework.com/docs/api/checkbox)
+
+#### Feedback y Alertas
+
+```typescript
+import { AlertController, ToastController, LoadingController } from '@ionic/angular';
+
+constructor(
+ private alertCtrl: AlertController,
+ private toastCtrl: ToastController,
+ private loadingCtrl: LoadingController
+) {}
+
+async mostrarAlerta() {
+ const alert = await this.alertCtrl.create({
+ header: 'Alerta',
+ subHeader: 'Información importante',
+ message: '¿Estás seguro de realizar esta acción?',
+ buttons: ['Cancelar', 'Aceptar']
+ });
+ await alert.present();
+}
+
+async mostrarToast() {
+ const toast = await this.toastCtrl.create({
+ message: 'Operación completada con éxito',
+ duration: 2000,
+ position: 'bottom',
+ color: 'success'
+ });
+ toast.present();
+}
+
+async mostrarCargando() {
+ const loading = await this.loadingCtrl.create({
+ message: 'Cargando datos...',
+ duration: 2000
+ });
+ await loading.present();
+}
+```
+
+> **Nota:** Para más información sobre alertas y elementos interactivos, consulte:
+> - [Ionic Alert](https://ionicframework.com/docs/api/alert)
+> - [Ionic Toast](https://ionicframework.com/docs/api/toast)
+> - [Ionic Loading](https://ionicframework.com/docs/api/loading)
+
+## 7. Capacitor: El Puente Nativo
+
+Capacitor es el framework que permite a Ionic acceder a las capacidades nativas del dispositivo:
+
+- Cámara y galería de fotos
+- Geolocalización
+- Almacenamiento persistente
+- Notificaciones push
+- Sensores del dispositivo
+- Archivos y sistema de archivos
+
+Ejemplo básico de uso de Capacitor:
+
+```typescript
+import { Camera, CameraResultType } from '@capacitor/camera';
+
+async function tomarFoto() {
+ const imagen = await Camera.getPhoto({
+ quality: 90,
+ allowEditing: true,
+ resultType: CameraResultType.Uri
+ });
+
+ // Usar la imagen (imagen.webPath)
+ const imagenUrl = imagen.webPath;
+}
+```
+
+## 8. Ciclo de Desarrollo con Ionic y Angular
+
+El proceso típico de desarrollo con Ionic y Angular incluye:
+
+1. **Creación del proyecto**:
+ ```bash
+ # Crear un nuevo proyecto (por defecto usa Angular)
+ ionic start mi-proyecto-ionic
+
+ # Especificar el framework (angular, react, vue)
+ ionic start mi-proyecto-ionic --type=angular
+ ```
+
+2. **Desarrollo en navegador web**:
+ ```bash
+ # Iniciar servidor de desarrollo con recarga en vivo
+ ionic serve
+ ```
+
+3. **Pruebas en dispositivo real**:
+ ```bash
+ # Añadir plataforma Android
+ ionic capacitor add android
+
+ # Ejecutar en dispositivo con recarga en vivo
+ ionic capacitor run android --livereload
+ ```
+
+4. **Compilación para producción**:
+ ```bash
+ # Construir la aplicación optimizada
+ ionic build --prod
+
+ # Copiar los archivos a las plataformas nativas
+ npx cap copy
+
+ # Abrir el proyecto en Android Studio para ajustes finales
+ npx cap open android
+ ```
+
+## Resumen
+
+Angular e Ionic forman una poderosa combinación para desarrollar aplicaciones móviles multiplataforma:
+
+- Angular proporciona la estructura, organización y lógica
+- Ionic aporta componentes UI con aspecto nativo
+- Capacitor permite acceder a características nativas del dispositivo
+- El desarrollo es similar al web tradicional, pero más estructurado y optimizado
+
+En los siguientes capítulos, exploraremos más a fondo cada uno de estos conceptos y construiremos paso a paso una aplicación completa de reservas para un gimnasio, además de la creación básica de un proyecto vacío.
+
+## Recursos y Documentación
+
+- [Documentación oficial de Angular](https://angular.io/docs)
+- [Documentación de Ionic Framework](https://ionicframework.com/docs)
+- [Capacitor Docs](https://capacitorjs.com/docs)
+- [Angular CLI](https://cli.angular.io/)
+- [TypeScript](https://www.typescriptlang.org/docs/)
+
+
+# Taller: Desarrollo con Angular e Ionic
+
+## Capítulo 2: Componentes y Estructura de una Aplicación Angular/Ionic
+
+## 1. Estructura de una Aplicación Angular/Ionic
+
+Al crear un nuevo proyecto de Ionic con Angular mediante el comando `ionic start`, se genera una estructura de carpetas organizada:
+
+```
+mi-app/
+├── src/ # Código fuente principal
+│ ├── app/ # Lógica y componentes de la aplicación
+│ │ ├── app.component.ts
+│ │ ├── app.module.ts
+│ │ ├── app-routing.module.ts
+│ ├── assets/ # Recursos estáticos (imágenes, fuentes, etc.)
+│ ├── environments/ # Configuraciones por entorno (dev, prod)
+│ ├── theme/ # Variables globales de estilo (colores, etc.)
+│ ├── global.scss # Estilos globales
+│ ├── index.html # Archivo HTML raíz
+│ ├── main.ts # Punto de entrada de la aplicación
+├── angular.json # Configuración de Angular
+├── capacitor.config.ts # Configuración de Capacitor
+├── package.json # Dependencias y scripts
+├── tsconfig.json # Configuración de TypeScript
+
+```
+
+### Archivos Clave:
+
+- **app.module.ts**: Módulo principal que configura la aplicación
+- **app-routing.module.ts**: Define las rutas de navegación
+- **app.component.ts**: Componente raíz que contiene toda la aplicación
+- **index.html**: Archivo HTML base donde se monta la aplicación
+- **main.ts**: Punto de entrada que arranca la aplicación Angular
+
+## 2. Fundamentos de los Componentes Angular
+
+### Anatomía de un Componente
+
+Cada componente Angular consta de:
+
+1. **Clase TypeScript**: Contiene la lógica y datos
+2. **Plantilla HTML**: Define la estructura visual
+3. **Estilos CSS**: Define la apariencia (opcional)
+4. **Metadatos**: Configuración mediante decorador @Component
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-producto', // Cómo se referencia en HTML
+ templateUrl: './producto.component.html', // Plantilla HTML
+ styleUrls: ['./producto.component.scss'] // Estilos
+})
+export class ProductoComponent implements OnInit {
+ nombre: string = 'Smartphone';
+ precio: number = 599.99;
+ disponible: boolean = true;
+
+ constructor() { }
+
+ ngOnInit() {
+ // Inicialización del componente
+ }
+
+ aplicarDescuento(porcentaje: number): void {
+ this.precio = this.precio * (1 - porcentaje/100);
+ }
+}
+
+```
+
+### Ciclo de Vida de los Componentes
+
+Los componentes tienen un ciclo de vida gestionado por Angular:
+
+1. **ngOnChanges**: Cuando cambian las propiedades de entrada (@Input)
+2. **ngOnInit**: Después de la primera inicialización
+3. **ngAfterViewInit**: Cuando la vista se ha inicializado
+4. **ngOnDestroy**: Justo antes de que Angular destruya el componente
+
+```typescript
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { DatosService } from './datos.service';
+
+@Component({
+ selector: 'app-ejemplo',
+ template: '
{{datos}}
'
+})
+export class EjemploComponent implements OnInit, OnDestroy {
+ datos: any[] = [];
+ private suscripcion!: Subscription;
+
+ constructor(private datosService: DatosService) { }
+
+ ngOnInit() {
+ // Perfecto para inicializar datos, suscripciones, etc.
+ this.suscripcion = this.datosService.obtenerDatos()
+ .subscribe(datos => {
+ this.datos = datos;
+ });
+ }
+
+ ngOnDestroy() {
+ // Limpieza antes de destruir el componente
+ if (this.suscripcion) {
+ this.suscripcion.unsubscribe();
+ }
+ }
+}
+
+```
+
+## 3. Vinculación de Datos (Data Binding)
+
+Angular ofrece cuatro formas de vinculación de datos:
+
+### Interpolación {{ }}
+
+Muestra valores de propiedades en la plantilla:
+
+```html
+
{{ titulo }}
+
Precio: {{ precio | currency }}
+
Estado: {{ disponible ? 'En stock' : 'Agotado' }}
+
+```
+
+### Property Binding [ ]
+
+Vincula propiedades HTML con valores del componente:
+
+```html
+
+
+
+ Contenido con clases dinámicas
+
+
+```
+
+### Event Binding ( )
+
+Responde a eventos del usuario:
+
+```html
+
+
+
+
+```
+
+### Two-Way Binding [( )]
+
+Combina property binding y event binding para actualizar datos en ambas direcciones:
+
+```html
+
+
+
+
+
+```
+
+> Nota: Para usar ngModel, debes importar FormsModule en tu módulo Angular.
+
+## 4. Directivas en Angular
+
+Las directivas son clases que extienden HTML con nueva funcionalidad.
+
+### Directivas Estructurales
+
+Modifican el DOM añadiendo o quitando elementos:
+
+```html
+
+