Skip to content

Actividad 1 - Navegación en Ionic

En esta actividad se van a desarrollar las diferentes formas de navegar entre páginas en una aplicación Ionic. La navegación es un aspecto fundamental en el desarrollo de aplicaciones móviles, ya que permite a los usuarios moverse entre diferentes vistas y funcionalidades de la aplicación.

Es la forma más común para aplicaciones móviles, ya que se encarga automáticamente de gestionar la pila de navegación, lo que facilita la implementación de transiciones y animaciones entre páginas. Además, es compatible con las características específicas de Ionic, como el manejo de gestos y la integración con componentes nativos.

import { NavController } from '@ionic/angular/standalone';
constructor(private navCtrl: NavController) {}
goToDetails() {
this.navCtrl.navigateForward('/details');
}
MétodoUso PrincipalVentaja Móvil
NavControllerLógica en el .tsControl total de animaciones y Stack
RouterLinkDirectamente en el .htmlMás rápido de implementar y leer
RouterLógica compleja o ServiciosEstándar de Angular, compatible con Guards

Se debe crear una aplicación Ionic con las siguientes características:

2.1. Configuración del Login y Rutas Principales

Section titled “2.1. Configuración del Login y Rutas Principales”

Configurar las rutas principales de la aplicación y la pantalla inicial de inicio de sesión que se muestra fuera del sistema de pestañas.

  1. Página de Login fuera de las pestañas

    La página de Login se encuentra fuera de las pestañas gracias a la configuración de rutas en src/app/app.routes.ts:

    src/app/app.routes.ts
    import { Routes } from '@angular/router';
    export const routes: Routes = [
    {
    path: '',
    loadComponent: () => import('./features/login/login.page').then((m) => m.LoginPage),
    },
    {
    path: 'login',
    loadComponent: () => import('./features/login/login.page').then((m) => m.LoginPage),
    },
    {
    path: 'tabs',
    loadChildren: () => import('./features/tabs/tabs.routes').then((m) => m.routes),
    },
    ];

    Explicación:

    • La ruta por defecto path: '' y la ruta path: 'login' cargan el componente Login directamente como ruta raíz.
    • La ruta path: 'tabs' carga el sistema de pestañas junto con todas sus rutas hijas.
  2. Componente Login

    La página de Login se encuentra en src/app/features/login/login.page.ts:

    src/app/features/login/login.page.ts
    import { Component, inject } from '@angular/core';
    import { Router } from '@angular/router';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonInput } from '@ionic/angular/standalone';
    @Component({
    selector: 'app-login',
    templateUrl: 'login.page.html',
    styleUrls: ['login.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonInput],
    })
    export class LoginPage {
    private router = inject(Router);
    login() {
    this.router.navigate(['/tabs']);
    }
    }

    Y su template en src/app/features/login/login.page.html:

    src/app/features/login/login.page.html
    <ion-header [translucent]="true">
    <ion-toolbar>
    <ion-title>
    Login
    </ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-content [fullscreen]="true">
    <ion-header collapse="condense">
    <ion-toolbar>
    <ion-title size="large">Login</ion-title>
    </ion-toolbar>
    </ion-header>
    <div class="login-container">
    <h2>Welcome</h2>
    <p>Please sign in to continue</p>
    <ion-input label="Username" labelPlacement="stacked" type="text" placeholder="Enter username" fill="outline" class="login-input"></ion-input>
    <ion-input label="Password" labelPlacement="stacked" type="password" placeholder="Enter password" fill="outline" class="login-input"></ion-input>
    <ion-button expand="block" class="login-btn" (click)="login()">Sign In</ion-button>
    </div>
    </ion-content>

Configurar la estructura base de la aplicación con tres pestañas (Clients, Reservations y Products), estableciendo la segunda como la vista por defecto al iniciar.

  1. Crear el template HTML

    Crear o modificar el archivo tabs.page.html con el componente ion-tabs:

    src/app/features/tabs/tabs.page.html
    <ion-tabs>
    <ion-tab-bar slot="bottom">
    <ion-tab-button tab="tab1" href="/tabs/tab1">
    <ion-icon aria-hidden="true" name="people"></ion-icon>
    <ion-label>Clients</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="tab2" href="/tabs/tab2">
    <ion-icon aria-hidden="true" name="calendar"></ion-icon>
    <ion-label>Reservations</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="tab3" href="/tabs/tab3">
    <ion-icon aria-hidden="true" name="cube"></ion-icon>
    <ion-label>Products</ion-label>
    </ion-tab-button>
    </ion-tab-bar>
    </ion-tabs>
  2. Definir las rutas

    Crear el archivo tabs.routes.ts con la configuración de rutas:

    src/app/features/tabs/tabs.routes.ts
    import { Routes } from '@angular/router';
    export const routes: Routes = [
    {
    path: '',
    loadComponent: () =>
    import('./tabs.page').then((m) => m.TabsPage),
    children: [
    {
    path: 'tab1',
    loadComponent: () =>
    import('../clients/clients.page').then((m) => m.ClientsPage),
    },
    {
    path: 'tab2',
    loadComponent: () =>
    import('../reservations/reservations.page').then((m) => m.ReservationsPage),
    },
    {
    path: 'tab3',
    loadComponent: () =>
    import('../products/products.page').then((m) => m.ProductsPage),
    },
    {
    path: 'client/:id',
    loadComponent: () =>
    import('../clients/client-detail.page').then((m) => m.ClientDetailPage),
    },
    {
    path: 'reservations/create',
    loadComponent: () =>
    import('../reservations/reservation-form.page').then((m) => m.ReservationFormPage),
    },
    {
    path: '',
    redirectTo: '/tabs/tab2',
    pathMatch: 'full',
    },
    ],
    },
    {
    path: '',
    redirectTo: '/tabs/tab2',
    pathMatch: 'full',
    },
    ];
  3. Configurar las pestañas por defecto

    La pestaña por defecto se configura mediante redirección del router:

    src/app/features/tabs/tabs.routes.ts
    {
    path: '',
    redirectTo: '/tabs/tab2',
    pathMatch: 'full',
    }
  4. Crear el componente TypeScript

    Crear el archivo tabs.page.ts:

    src/app/features/tabs/tabs.page.ts
    import { Component } from '@angular/core';
    import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone';
    import { addIcons } from 'ionicons';
    import { people, calendar, cube } from 'ionicons/icons';
    @Component({
    selector: 'app-tabs',
    templateUrl: 'tabs.page.html',
    styleUrls: ['tabs.page.scss'],
    imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel],
    })
    export class TabsPage {
    constructor() {
    addIcons({ people, calendar, cube });
    }
    }

Añadir y configurar iconos adecuados en el HTML para representar visualmente el contenido de cada pestaña.

  1. Agregar iconos en el HTML

    Modificar src/app/features/tabs/tabs.page.html para usar ion-icon:

    src/app/features/tabs/tabs.page.html
    <ion-tabs>
    <ion-tab-bar slot="bottom">
    <ion-tab-button tab="tab1" href="/tabs/tab1">
    <ion-icon aria-hidden="true" name="people"></ion-icon>
    <ion-label>Clients</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="tab2" href="/tabs/tab2">
    <ion-icon aria-hidden="true" name="calendar"></ion-icon>
    <ion-label>Reservations</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="tab3" href="/tabs/tab3">
    <ion-icon aria-hidden="true" name="cube"></ion-icon>
    <ion-label>Products</ion-label>
    </ion-tab-button>
    </ion-tab-bar>
    </ion-tabs>
  2. Importar iconos en el componente

    En tabs.page.ts importar y registrar los iconos:

    src/app/features/tabs/tabs.page.ts
    import { Component } from '@angular/core';
    import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone';
    import { addIcons } from 'ionicons';
    import { people, calendar, cube } from 'ionicons/icons';
    @Component({
    selector: 'app-tabs',
    templateUrl: 'tabs.page.html',
    styleUrls: ['tabs.page.scss'],
    imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel],
    })
    export class TabsPage {
    constructor() {
    addIcons({ people, calendar, cube });
    }
    }

Implementar la navegación desde la lista de clientes hacia una página de detalle utilizando routerLink, enviando el ID del cliente para recibirlo mediante el decorador @Input().

  1. Definir datos del componente

    Crear src/app/features/clients/clients.page.ts con la interfaz Client y el array de ejemplo:

    src/app/features/clients/clients.page.ts
    import { Component } from '@angular/core';
    import { RouterLink } from '@angular/router';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel } from '@ionic/angular/standalone';
    interface Client {
    id: number;
    name: string;
    email: string;
    phone: string;
    }
    @Component({
    selector: 'app-clients',
    templateUrl: 'clients.page.html',
    styleUrls: ['clients.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, RouterLink],
    })
    export class ClientsPage {
    clients: Client[] = [
    { id: 1, name: 'John Doe', email: 'john@example.com', phone: '+1 234 567 890' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com', phone: '+1 234 567 891' },
    { id: 3, name: 'Bob Johnson', email: 'bob@example.com', phone: '+1 234 567 892' },
    { id: 4, name: 'Alice Brown', email: 'alice@example.com', phone: '+1 234 567 893' },
    { id: 5, name: 'Charlie Wilson', email: 'charlie@example.com', phone: '+1 234 567 894' },
    ];
    constructor() {}
    }
  2. Crear template del listado con routerLink

    En src/app/features/clients/clients.page.html usar [routerLink] para navegar al detalle:

    src/app/features/clients/clients.page.html
    <ion-header [translucent]="true">
    <ion-toolbar>
    <ion-buttons slot="start">
    <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>
    Clients
    </ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-content [fullscreen]="true">
    <ion-header collapse="condense">
    <ion-toolbar>
    <ion-title size="large">Clients</ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-list>
    @for (client of clients; track client.id) {
    <ion-item [routerLink]="['/tabs/client', client.id]">
    <ion-label>
    <h2>{{ client.name }}</h2>
    <p>{{ client.email }}</p>
    <p>{{ client.phone }}</p>
    </ion-label>
    </ion-item>
    }
    </ion-list>
    </ion-content>
  3. Crear página de detalle con @Input

    En src/app/features/clients/client-detail.page.ts usar el decorador @Input() para recibir el ID:

    src/app/features/clients/client-detail.page.ts
    import { Component, Input } from '@angular/core';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonButton, IonIcon } from '@ionic/angular/standalone';
    import { addIcons } from 'ionicons';
    import { arrowBack } from 'ionicons/icons';
    @Component({
    selector: 'app-client-detail',
    templateUrl: 'client-detail.page.html',
    styleUrls: ['client-detail.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonButton, IonIcon],
    })
    export class ClientDetailPage {
    @Input() id?: string;
    constructor() {
    addIcons({ arrowBack });
    }
    goBack() {
    window.history.back();
    }
    }
  4. Crear template del detalle

    En src/app/features/clients/client-detail.page.html mostrar el ID recibido:

    src/app/features/clients/client-detail.page.html
    <ion-header [translucent]="true">
    <ion-toolbar>
    <ion-buttons slot="start">
    <ion-button (click)="goBack()" fill="clear">
    <ion-icon slot="icon-only" name="arrow-back"></ion-icon>
    </ion-button>
    </ion-buttons>
    <ion-title>
    Client Details
    </ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-content [fullscreen]="true">
    <ion-header collapse="condense">
    <ion-toolbar>
    <ion-title size="large">Client Details</ion-title>
    </ion-toolbar>
    </ion-header>
    <div class="detail-container">
    <h2>Cliente ID: {{ id }}</h2>
    </div>
    </ion-content>

2.5. Pestaña Reservations (Listado y Creación)

Section titled “2.5. Pestaña Reservations (Listado y Creación)”

Desarrollar la vista de reservas incluyendo un listado de datos de prueba y la funcionalidad para navegar a un formulario de creación utilizando NavController.

  1. Definir datos del componente

    Crear src/app/features/reservations/reservations.page.ts con la interfaz Reservation y el array de ejemplo:

    src/app/features/reservations/reservations.page.ts
    import { Component } from '@angular/core';
    import { NavController } from '@ionic/angular/standalone';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton } from '@ionic/angular/standalone';
    interface Reservation {
    id: number;
    clientName: string;
    service: string;
    date: string;
    time: string;
    }
    @Component({
    selector: 'app-reservations',
    templateUrl: 'reservations.page.html',
    styleUrls: ['reservations.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton],
    })
    export class ReservationsPage {
    constructor(private navCtrl: NavController) {
    this.navCtrl = navCtrl;
    }
    reservations: Reservation[] = [
    { id: 1, clientName: 'John Doe', service: 'Haircut', date: '2024-03-15', time: '10:00 AM' },
    { id: 2, clientName: 'Jane Smith', service: 'Manicure', date: '2024-03-15', time: '2:00 PM' },
    { id: 3, clientName: 'Bob Johnson', service: 'Massage', date: '2024-03-16', time: '11:30 AM' },
    { id: 4, clientName: 'Alice Brown', service: 'Facial', date: '2024-03-16', time: '3:00 PM' },
    { id: 5, clientName: 'Charlie Wilson', service: 'Hair Color', date: '2024-03-17', time: '9:00 AM' },
    ];
    createReservation() {
    this.navCtrl.navigateForward('/tabs/reservations/create');
    }
    }
  2. Navegación con navCtrl.navigateForward

    En el componente TypeScript, inyectar NavController y crear la función createReservation():

    src/app/features/reservations/reservations.page.ts
    import { Component } from '@angular/core';
    import { NavController } from '@ionic/angular/standalone';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton } from '@ionic/angular/standalone';
    interface Reservation {
    id: number;
    clientName: string;
    service: string;
    date: string;
    time: string;
    }
    @Component({
    selector: 'app-reservations',
    templateUrl: 'reservations.page.html',
    styleUrls: ['reservations.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton],
    })
    export class ReservationsPage {
    constructor(private navCtrl: NavController) {
    this.navCtrl = navCtrl;
    }
    reservations: Reservation[] = [
    { id: 1, clientName: 'John Doe', service: 'Haircut', date: '2024-03-15', time: '10:00 AM' },
    { id: 2, clientName: 'Jane Smith', service: 'Manicure', date: '2024-03-15', time: '2:00 PM' },
    { id: 3, clientName: 'Bob Johnson', service: 'Massage', date: '2024-03-16', time: '11:30 AM' },
    { id: 4, clientName: 'Alice Brown', service: 'Facial', date: '2024-03-16', time: '3:00 PM' },
    { id: 5, clientName: 'Charlie Wilson', service: 'Hair Color', date: '2024-03-17', time: '9:00 AM' },
    ];
    createReservation() {
    this.navCtrl.navigateForward('/tabs/reservations/create');
    }
    }
  3. Template del listado con botón

    En src/app/features/reservations/reservations.page.html usar ion-list y el botón:

    src/app/features/reservations/reservations.page.html
    <ion-header [translucent]="true">
    <ion-toolbar>
    <ion-buttons slot="start">
    <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>
    Reservations
    </ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-content [fullscreen]="true">
    <ion-header collapse="condense">
    <ion-toolbar>
    <ion-title size="large">Reservations</ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-list>
    @for (reservation of reservations; track reservation.id) {
    <ion-item>
    <ion-label>
    <h2>{{ reservation.clientName }}</h2>
    <p>{{ reservation.service }} - {{ reservation.date }} at {{ reservation.time }}</p>
    </ion-label>
    </ion-item>
    }
    </ion-list>
    <ion-button expand="block" (click)="createReservation()" class="create-btn">New Reservation</ion-button>
    </ion-content>

Mostrar un listado de productos e implementar la lógica de cerrar sesión.

  1. Definir datos del componente

    Crear src/app/features/products/products.page.ts con la interfaz Product y el array de ejemplo:

    src/app/features/products/products.page.ts
    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton } from '@ionic/angular/standalone';
    interface Product {
    id: number;
    name: string;
    price: number;
    description: string;
    }
    @Component({
    selector: 'app-products',
    templateUrl: 'products.page.html',
    styleUrls: ['products.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton],
    })
    export class ProductsPage {
    constructor(private router: Router) {
    this.router = router;
    }
    products: Product[] = [
    { id: 1, name: 'Shampoo', price: 15.99, description: 'Professional hair shampoo' },
    { id: 2, name: 'Conditioner', price: 16.99, description: 'Deep conditioning treatment' },
    { id: 3, name: 'Hair Oil', price: 24.99, description: 'Argan oil for hair care' },
    { id: 4, name: 'Face Mask', price: 12.99, description: 'Hydrating facial treatment' },
    { id: 5, name: 'Body Lotion', price: 18.99, description: 'Moisturizing body lotion' },
    ];
    logout() {
    localStorage.clear();
    this.router.navigate(['/login']);
    }
    }
  2. Cerrar Sesión con router.navigate

    En el componente TypeScript, inyectar Router y crear la función logout() que limpie localStorage y navegue a /login:

    src/app/features/products/products.page.ts
    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton } from '@ionic/angular/standalone';
    interface Product {
    id: number;
    name: string;
    price: number;
    description: string;
    }
    @Component({
    selector: 'app-products',
    templateUrl: 'products.page.html',
    styleUrls: ['products.page.scss'],
    imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton, IonList, IonItem, IonLabel, IonButton],
    })
    export class ProductsPage {
    constructor(private router: Router) {
    this.router = router;
    }
    products: Product[] = [
    { id: 1, name: 'Shampoo', price: 15.99, description: 'Professional hair shampoo' },
    { id: 2, name: 'Conditioner', price: 16.99, description: 'Deep conditioning treatment' },
    { id: 3, name: 'Hair Oil', price: 24.99, description: 'Argan oil for hair care' },
    { id: 4, name: 'Face Mask', price: 12.99, description: 'Hydrating facial treatment' },
    { id: 5, name: 'Body Lotion', price: 18.99, description: 'Moisturizing body lotion' },
    ];
    logout() {
    localStorage.clear();
    this.router.navigate(['/login']);
    }
    }
  3. Template del listado con botón de logout

    En src/app/features/products/products.page.html:

    src/app/features/products/products.page.html
    <ion-header [translucent]="true">
    <ion-toolbar>
    <ion-buttons slot="start">
    <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>
    Products
    </ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-content [fullscreen]="true">
    <ion-header collapse="condense">
    <ion-toolbar>
    <ion-title size="large">Products</ion-title>
    </ion-toolbar>
    </ion-header>
    <ion-list>
    @for (product of products; track product.id) {
    <ion-item>
    <ion-label>
    <h2>{{ product.name }}</h2>
    <p>${{ product.price }}</p>
    <p>{{ product.description }}</p>
    </ion-label>
    </ion-item>
    }
    </ion-list>
    <ion-button expand="block" color="danger" (click)="logout()" class="logout-btn">Cerrar Sesión</ion-button>
    </ion-content>

Integrar un diseño personalizado y adaptarlas a los componentes de Ionic.

  1. Generar tema en bearnie.dev

    Se ha utilizado la herramienta online bearnie.dev para generar un tema de colores personalizado estilo shadcn/ui. Esta herramienta genera variables CSS utilizando el espacio de color OKLCH para mejor gestión de colores.

  2. Definir variables del tema

    Crear src/theme/variables.scss con las variables generadas:

    src/theme/variables.scss
    // For information on how to create your own theme, please refer to:
    // https://ionicframework.com/docs/theming/
    /* Bearnie Theme - Generated at bearnie.dev/create-bearnie-theme */
    :root {
    --radius: 0.625rem;
    --spacing: 0.25rem;
    --font-sans: "Inter", sans-serif;
    --background: oklch(1 0 0);
    --foreground: oklch(0.147 0.004 49.25);
    --card: oklch(1 0 0);
    --card-foreground: oklch(0.147 0.004 49.25);
    --popover: oklch(1 0 0);
    --popover-foreground: oklch(0.147 0.004 49.25);
    --primary: oklch(0.546 0.245 262.881);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.97 0.001 106.424);
    --secondary-foreground: oklch(0.147 0.004 49.25);
    --muted: oklch(0.97 0.001 106.424);
    --muted-foreground: oklch(0.553 0.013 58.071);
    --accent: oklch(0.97 0.001 106.424);
    --accent-foreground: oklch(0.147 0.004 49.25);
    --destructive: oklch(0.577 0.245 27.325);
    --destructive-foreground: oklch(0.985 0 0);
    --border: oklch(0.923 0.003 48.717);
    --input: oklch(0.923 0.003 48.717);
    --ring: oklch(0.546 0.245 262.881);
    }
    .dark {
    --background: oklch(0.147 0.004 49.25);
    --foreground: oklch(0.97 0.001 106.424);
    --card: oklch(0.147 0.004 49.25);
    --card-foreground: oklch(0.97 0.001 106.424);
    --popover: oklch(0.147 0.004 49.25);
    --popover-foreground: oklch(0.97 0.001 106.424);
    --primary: oklch(0.546 0.245 262.881);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.268 0.007 34.298);
    --secondary-foreground: oklch(0.97 0.001 106.424);
    --muted: oklch(0.268 0.007 34.298);
    --muted-foreground: oklch(0.709 0.01 56.259);
    --accent: oklch(0.268 0.007 34.298);
    --accent-foreground: oklch(0.97 0.001 106.424);
    --destructive: oklch(0.577 0.245 27.325);
    --destructive-foreground: oklch(0.985 0 0);
    --border: oklch(0.268 0.007 34.298);
    --input: oklch(0.268 0.007 34.298);
    --ring: oklch(0.546 0.245 262.881);
    }

    Explicación de las variables:

    • --radius: Radio de borde para componentes (0.625rem)
    • --spacing: Espaciado base
    • --font-sans: Familia tipográfica (Inter)
    • Colores en notación OKLCH: --primary, --secondary, --muted, --destructive, etc.
  3. Mapear variables a Ionic

    En src/global.scss se mapean las variables shadcn a las variables de Ionic:

    src/global.scss
    /*
    * App Global CSS
    * ----------------------------------------------------------------------------
    * Put style rules here that you want to apply globally. These styles are for
    * the entire app and not just one component. Additionally, this file can be
    * used as an entry point to import other CSS/Sass files to be included in the
    * output CSS.
    * For more information on global stylesheets, visit the documentation:
    * https://ionicframework.com/docs/layout/global-stylesheets
    */
    /* Core CSS required for Ionic components to work properly */
    @import "@ionic/angular/css/core.css";
    /* Basic CSS for apps built with Ionic */
    @import "@ionic/angular/css/normalize.css";
    @import "@ionic/angular/css/structure.css";
    @import "@ionic/angular/css/typography.css";
    @import "@ionic/angular/css/display.css";
    /* Optional CSS utils that can be commented out */
    @import "@ionic/angular/css/padding.css";
    @import "@ionic/angular/css/float-elements.css";
    @import "@ionic/angular/css/text-alignment.css";
    @import "@ionic/angular/css/text-transformation.css";
    @import "@ionic/angular/css/flex-utils.css";
    /**
    * Ionic Dark Mode
    * -----------------------------------------------------
    * For more info, please see:
    * https://ionicframework.com/docs/theming/dark-mode
    */
    /* @import "@ionic/angular/css/palettes/dark.always.css"; */
    /* @import "@ionic/angular/css/palettes/dark.class.css"; */
    @import '@ionic/angular/css/palettes/dark.system.css';
    /* ============================================
    Bearnie Theme - Map shadcn variables to Ionic
    ============================================ */
    :root {
    /* Map shadcn colors to Ionic */
    --ion-color-primary: var(--primary);
    --ion-color-primary-contrast: var(--primary-foreground);
    --ion-color-secondary: var(--secondary);
    --ion-color-secondary-contrast: var(--secondary-foreground);
    --ion-color-danger: var(--destructive);
    --ion-color-danger-contrast: var(--destructive-foreground);
    /* Background & text */
    --ion-background-color: var(--background);
    --ion-text-color: var(--foreground);
    /* Cards & surfaces */
    --ion-item-background: var(--card);
    --ion-card-background: var(--card);
    /* Borders & inputs */
    --ion-border-color: var(--border);
    --ion-input-background: var(--input);
    /* Tab bar */
    --ion-tab-bar-background: var(--background);
    --ion-tab-bar-color: var(--muted-foreground);
    --ion-tab-bar-color-selected: var(--primary);
    /* Toolbar */
    --ion-toolbar-background: var(--background);
    --ion-toolbar-color: var(--foreground);
    }
    /* Dark mode */
    .dark, body.dark, :root[color-scheme="dark"] {
    --ion-color-primary: var(--primary);
    --ion-color-primary-contrast: var(--primary-foreground);
    --ion-color-secondary: var(--secondary);
    --ion-color-secondary-contrast: var(--secondary-foreground);
    --ion-color-danger: var(--destructive);
    --ion-color-danger-contrast: var(--destructive-foreground);
    --ion-background-color: var(--background);
    --ion-text-color: var(--foreground);
    --ion-item-background: var(--card);
    --ion-card-background: var(--card);
    --ion-border-color: var(--border);
    --ion-input-background: var(--input);
    --ion-tab-bar-background: var(--background);
    --ion-tab-bar-color: var(--muted-foreground);
    --ion-tab-bar-color-selected: var(--primary);
    --ion-toolbar-background: var(--background);
    --ion-toolbar-color: var(--foreground);
    }
    /* Apply border radius to Ionic components */
    ion-button, ion-card, ion-input, ion-item, ion-chip {
    --border-radius: var(--radius);
    }
    /* Global font */
    body, html, ion-content, ion-label, ion-text {
    --font-family: var(--font-sans);
    }

    Explicación del mapeo:

    • --ion-color-primary--primary (color principal de la app)
    • --ion-color-danger--destructive (color de peligro/eliminar)
    • --ion-background-color--background (fondo de la app)
    • --ion-text-color--foreground (color del texto)
    • --ion-item-background--card (fondo de items y cards)
    • --ion-tab-bar-* → Configuración de la barra de pestañas
  4. Aplicar estilos a componentes

    El border-radius se aplica globalmente a los componentes Ionic:

    ion-button,
    ion-card,
    ion-input,
    ion-item,
    ion-chip {
    --border-radius: var(--radius);
    }
  1. Clonar el repositorio

    Terminal window
    git clone https://github.com/aek676/dah-2026.git
    cd dah-2026
  2. Instalar dependencias y ejecutar

    Terminal window
    npm install
    ionic serve
  3. Esto abrirá la aplicación en el navegador por defecto en http://localhost:8100.

3.2. Estructura y descripción de pantallas

Section titled “3.2. Estructura y descripción de pantallas”

La aplicación está compuesta por las siguientes pantallas:

  • Login: Pantalla inicial de la aplicación. Se muestra fuera de las pestañas y contiene dos campos de texto (usuario y contraseña) y un botón Sign In. Al pulsar el botón, se navega a las pestañas principales de la aplicación mediante router.navigate(['/tabs']).

    Pantalla de Login
  • Clients (Pestaña 1): Muestra un listado de clientes con su nombre, email y teléfono. Cada ítem de la lista es un enlace (routerLink) que envía el ID del cliente a una página de detalle.

    Listado de clientes
  • Detalle de Cliente: Página accesible desde la lista de clientes. Recibe el ID del cliente mediante @Input() y muestra la información del cliente seleccionado. Incluye un botón de retroceso (ion-back-button) para volver al listado.

    Detalle de cliente
  • Reservations (Pestaña 2): Es la pestaña por defecto al iniciar la aplicación. Muestra un listado de reservas con el nombre del cliente, la fecha y el servicio. Incluye un botón }Crear Reserva} que utiliza navCtrl.navigateForward para navegar al formulario de creación.

    Listado de reservas
  • Formulario de Reserva: Página accesible desde la pestaña de reservas. Contiene un formulario para crear una nueva reserva.

    Formulario de reserva
  • Products (Pestaña 3): Muestra un listado de productos con su nombre, precio y descripción. Al final de la lista se encuentra el botón }Cerrar Sesión} de color rojo que permite salir de la aplicación.

    Listado de productos

El flujo principal de la aplicación es el siguiente:

  1. El usuario accede a la aplicación y se muestra la pantalla de Login
  2. Al pulsar Sign In, se navega a las pestañas (por defecto se muestra Reservations)
  3. Desde las pestañas, el usuario puede:
    • En Clients: pulsar un cliente para ver su detalle y volver con el botón atrás
    • En Reservations: pulsar Crear Reserva para acceder al formulario y volver con el botón atrás
    • En Products: consultar los productos o pulsar Cerrar Sesión
  4. Al pulsar Cerrar Sesión, se ejecuta localStorage.clear() para simular la limpieza de datos de sesión y se utiliza router.navigate(['/login']) para redirigir al usuario a la pantalla de Login, que se encuentra fuera de las pestañas