Skip to content

SergioAcostaTer/canary-beaches-platform

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

402 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🏖️ Playea - Plataforma de Playas de Canarias

Angular TypeScript Ionic Firebase License

Una plataforma web moderna y responsiva para explorar, valorar y descubrir las mejores playas de las Islas Canarias.

📋 Tabla de Contenidos


🌊 Descripción General

Playea es una aplicación web full-stack que permite a los usuarios explorar, valorar y gestionar información sobre las playas de las Islas Canarias. La plataforma ofrece una experiencia completa con autenticación de usuarios, sistema de favoritos, comentarios y reseñas, visualización de mapas interactivos, datos meteorológicos en tiempo real y predicciones de mareas.

🎯 Objetivos del Proyecto

  1. Centralizar información sobre todas las playas canarias
  2. Facilitar la búsqueda mediante filtros avanzados y geolocalización
  3. Proporcionar datos en tiempo real de clima y mareas
  4. Crear una comunidad donde usuarios compartan experiencias
  5. Optimizar la experiencia en dispositivos móviles y de escritorio

✨ Características Principales

🔐 Autenticación y Usuarios

  • ✅ Registro e inicio de sesión con email/contraseña
  • ✅ Autenticación con Google y Apple (integración OAuth)
  • ✅ Recuperación de contraseña mediante OTP
  • ✅ Perfiles de usuario personalizables (foto, nombre, apellido)
  • ✅ Gestión de sesiones con Firebase Authentication

🏝️ Exploración de Playas

  • ✅ Catálogo completo de 700+ playas canarias
  • ✅ Búsqueda por nombre, isla o características
  • ✅ Filtros avanzados (bandera azul, arena/roca, servicios)
  • ✅ Ordenamiento por distancia del usuario
  • ✅ Visualización en tarjetas responsivas con imágenes

⭐ Sistema de Valoraciones

  • ✅ Comentarios y reseñas con calificación 1-5 estrellas
  • ✅ Carga de imágenes en comentarios
  • ✅ Edición y eliminación de propias reseñas
  • ✅ Cálculo automático de calificación promedio
  • ✅ Visualización de reseñas con timestamps relativos

📍 Mapas y Geolocalización

  • ✅ Integración con MapLibre GL para mapas interactivos
  • ✅ Visualización de ubicación de playas con marcadores
  • ✅ Cálculo de distancia desde ubicación del usuario
  • ✅ Vista de mapa general con todas las playas
  • ✅ Imágenes satelitales de alta calidad (MapTiler)

🌤️ Datos Meteorológicos

  • ✅ Pronóstico del tiempo para 7 días (Open-Meteo API)
  • ✅ Temperaturas máximas y mínimas
  • ✅ Probabilidad de precipitación
  • ✅ Códigos meteorológicos con íconos visuales
  • ✅ Actualización automática de datos

🌊 Predicción de Mareas

  • ✅ Datos de mareas del Instituto Hidrográfico de la Marina
  • ✅ Gráficos interactivos con Chart.js
  • ✅ Indicador de hora actual en gráfico
  • ✅ Cálculo del puerto más cercano a cada playa
  • ✅ Altura de marea en tiempo real

❤️ Favoritos

  • ✅ Guardar playas favoritas por usuario
  • ✅ Vista dedicada de playas guardadas
  • ✅ Sincronización en tiempo real con Firebase
  • ✅ Indicador visual en tarjetas de playas

🏆 Rankings

  • ✅ Ranking de playas con Bandera Azul
  • ✅ Rankings por isla
  • ✅ Ordenamiento por calificación de usuarios
  • ✅ Visualización de estadísticas de ocupación

🛠️ Tecnologías Utilizadas

Frontend

Framework Principal

  • Angular 19.1 - Framework web modular
  • TypeScript 5.7 - Tipado estático
  • Ionic 8.0 - Componentes UI móviles
  • Tailwind CSS 4.1 - Estilos utility-first

Librerías UI/UX

  • MapLibre GL 5.2 - Mapas interactivos
  • Chart.js 4.4 - Gráficos de mareas
  • ng2-charts 5.0 - Wrapper Angular para Chart.js
  • ngx-sonner 3.1 - Notificaciones toast elegantes

Utilidades

  • RxJS 7.8 - Programación reactiva
  • date-fns 4.1 - Manipulación de fechas
  • Axios 1.8 - Cliente HTTP

Backend y Servicios

Base de Datos y Autenticación

  • Firebase 11.6 - Suite completa de backend
    • Firestore - Base de datos NoSQL en tiempo real
    • Authentication - Gestión de usuarios
    • Storage - Almacenamiento de imágenes
    • AngularFire2 5.4 - SDK Angular para Firebase

APIs Externas

  • Open-Meteo API - Datos meteorológicos gratuitos
  • Instituto Hidrográfico de la Marina - Datos de mareas
  • MapTiler - Imágenes satelitales para mapas

Herramientas de Desarrollo

  • Angular CLI 19.1 - Gestión de proyecto
  • Karma + Jasmine - Testing unitario
  • ESLint - Linting de código
  • Capacitor 7.2 - Wrapper nativo para móviles

🏗️ Arquitectura del Proyecto

Arquitectura General

┌─────────────────────────────────────────────────┐
│              CLIENTE (Angular)                   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐      │
│  │  Pages   │  │Components│  │ Services │      │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘      │
│       │             │              │            │
│       └─────────────┴──────────────┘            │
│                     │                           │
└─────────────────────┼───────────────────────────┘
                      │
        ┌─────────────┼─────────────┐
        │             │             │
        ▼             ▼             ▼
┌──────────┐   ┌──────────┐   ┌──────────┐
│ Firebase │   │ Open     │   │ IHM API  │
│ Firestore│   │ Meteo    │   │ (Mareas) │
└──────────┘   └──────────┘   └──────────┘

Patrón de Arquitectura

MVVM (Model-View-ViewModel) con inyección de dependencias:

  1. Models (src/models/) - Interfaces TypeScript que definen la estructura de datos
  2. Views (Templates HTML) - Presentación de la UI con data binding
  3. ViewModels (Componentes) - Lógica de presentación y estado
  4. Services (src/services/) - Lógica de negocio y acceso a datos

Flujo de Datos

// Ejemplo: Cargar playas
Component  Service  Firebase/API  Observable  Component  Template
// beach.service.ts
getAllBeaches(): Observable<Beach[]> {
  return this.firestore.collection<Beach>('beaches').valueChanges();
}

// home.component.ts
this.beachService.getAllBeaches().subscribe(beaches => {
  this.beaches = beaches;
});

📦 Instalación y Configuración

Requisitos Previos

  • Node.js >= 18.x
  • npm >= 9.x
  • Angular CLI >= 19.x
  • Cuenta de Firebase (Plan Spark o superior)

Instalación

# 1. Clonar el repositorio
git clone https://github.com/your-repo/playea.git
cd playea/client

# 2. Instalar dependencias
npm install

# 3. Configurar variables de entorno
cp src/environments/environment.example.ts src/environments/environment.ts

# 4. Editar environment.ts con tus credenciales de Firebase

Configuración de Firebase

// src/environments/environment.ts
export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "TU_API_KEY",
    authDomain: "TU_PROJECT.firebaseapp.com",
    projectId: "TU_PROJECT_ID",
    storageBucket: "TU_PROJECT.appspot.com",
    messagingSenderId: "TU_SENDER_ID",
    appId: "TU_APP_ID",
    measurementId: "TU_MEASUREMENT_ID"
  },
  apiBaseUrl: 'http://localhost:4200'
};

Ejecución en Desarrollo

# Servidor de desarrollo (puerto 4200)
npm start
# o
ng serve

# Abrir en navegador
# http://localhost:4200

Compilación para Producción

# Build optimizado
npm run build

# Los archivos compilados estarán en dist/playa/browser/

📂 Estructura del Proyecto

client/
├── public/                    # Archivos estáticos
│   ├── images/               # Imágenes del sitio
│   └── mockup/               # Datos de prueba JSON
│
├── src/
│   ├── app/
│   │   ├── components/       # Componentes reutilizables
│   │   │   ├── beach-card/
│   │   │   ├── beach-comments/
│   │   │   ├── beach-detail-layout/
│   │   │   ├── beach-grid/
│   │   │   ├── category/
│   │   │   ├── comment-item/
│   │   │   ├── filter-panel/
│   │   │   ├── footer/
│   │   │   ├── header/
│   │   │   ├── maplibre-map/
│   │   │   ├── ranking-card/
│   │   │   ├── tides-status/
│   │   │   ├── user-header/
│   │   │   └── weather-display/
│   │   │
│   │   ├── layout/           # Componentes de diseño
│   │   │   ├── main/         # Layout con header/footer
│   │   │   └── noheader/     # Layout sin header
│   │   │
│   │   ├── models/           # Interfaces TypeScript
│   │   │   ├── beach.ts
│   │   │   ├── category.ts
│   │   │   ├── comment.ts
│   │   │   ├── user.ts
│   │   │   └── weather.ts
│   │   │
│   │   ├── pages/            # Páginas de la aplicación
│   │   │   ├── beachDetail/
│   │   │   ├── favourites/
│   │   │   ├── forgot-password/
│   │   │   ├── general-map/
│   │   │   ├── home/
│   │   │   ├── login/
│   │   │   ├── otp-verification/
│   │   │   ├── profile/
│   │   │   ├── ranking/
│   │   │   ├── rankingByIsland/
│   │   │   ├── register/
│   │   │   ├── search/
│   │   │   └── view-profile/
│   │   │
│   │   ├── services/         # Servicios de datos
│   │   │   ├── auth.service.ts
│   │   │   ├── beach.service.ts
│   │   │   ├── favourites.service.ts
│   │   │   ├── review.service.ts
│   │   │   ├── tide.service.ts
│   │   │   ├── user.service.ts
│   │   │   └── weather.service.ts
│   │   │
│   │   ├── utils/            # Utilidades
│   │   │   ├── toggle-password-view.ts
│   │   │   └── validators.ts
│   │   │
│   │   ├── app.component.ts
│   │   ├── app.config.ts
│   │   └── app.routes.ts
│   │
│   ├── environments/         # Configuración por entorno
│   ├── styles/              # Estilos globales
│   ├── index.html
│   ├── main.ts
│   └── server.ts            # SSR (opcional)
│
├── angular.json             # Configuración de Angular
├── package.json
├── tsconfig.json
├── tailwind.config.js       # Configuración de Tailwind
└── README.md

🎨 Funcionalidades Clave

1. Sistema de Búsqueda Avanzada

Ubicación: src/app/pages/search/

// Búsqueda con filtros múltiples
onFiltersChange(filters: any) {
  this.filters = {
    hasLifeguard: filters.hasLifeguard,
    hasSand: filters.hasSand,
    hasRock: filters.hasRock,
    hasShowers: filters.hasShowers,
    hasToilets: filters.hasToilets,
    hasFootShowers: filters.hasFootShowers
  };
  this.triggerSearch();
}

// Debounce para optimizar peticiones
this.searchSubject.pipe(
  debounceTime(500),
  switchMap(({ query, island, filters }) => {
    return this.searchBeachService.searchBeaches(query, { island, ...filters });
  })
).subscribe(beaches => this.beaches = beaches);

Características:

  • Búsqueda en tiempo real con debounce (500ms)
  • Filtros por isla, servicios, tipo de arena
  • Sincronización con URL query params
  • Resultados paginados (opcional)

2. Geolocalización y Distancias

Ubicación: src/app/components/beach-card/

// Calcular distancia del usuario a la playa
private calculateDistance(
  lat1: number, lon1: number, 
  lat2: number, lon2: number
): string {
  const R = 6371; // Radio de la Tierra en km
  const dLat = this.degToRad(lat2 - lat1);
  const dLon = this.degToRad(lon2 - lon1);
  
  const a = 
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(this.degToRad(lat1)) * Math.cos(this.degToRad(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
    
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = R * c;
  
  return `${distance.toFixed(2)} km`;
}

3. Sistema de Comentarios con Imágenes

Ubicación: src/app/components/beach-comments/

// Crear comentario con imagen
async onAddComment(): Promise<void> {
  const formData = new FormData();
  formData.append('beachId', this.beachId);
  formData.append('rating', this.newCommentRating.toString());
  formData.append('comment', this.newCommentText);
  
  if (this.selectedImageFile) {
    formData.append('image', this.selectedImageFile);
  }

  this.reviewService.createReview(formData).subscribe({
    next: (createdReview) => {
      this.reviews.push(createdReview.review);
      toast.success('Comentario añadido correctamente');
    },
    error: (error) => toast.error('Error al añadir el comentario')
  });
}

Características:

  • Subida de imágenes (max 5MB)
  • Validación de tipo de archivo
  • Previsualización antes de enviar
  • Edición con opción de cambiar/eliminar imagen

4. Mapas Interactivos

Ubicación: src/app/components/maplibre-map/

private initMap(): void {
  this.map = new maplibre.Map({
    container: 'map',
    style: 'https://api.maptiler.com/maps/satellite/style.json?key=YOUR_KEY',
    center: [this.longitude, this.latitude],
    zoom: this.zoom,
    attributionControl: false
  });

  // Añadir marcador de la playa
  new maplibre.Marker()
    .setLngLat([this.longitude, this.latitude])
    .addTo(this.map);
}

5. Pronóstico del Tiempo (7 días)

Ubicación: src/app/components/weather-display/

private fetchWeatherData(): void {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${this.latitude}&longitude=${this.longitude}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode&timezone=auto`;

  fetch(url)
    .then(response => response.json())
    .then(data => {
      this.weatherDays = data?.daily?.time.map((date: string, index: number) => ({
        date,
        tempMax: data.daily.temperature_2m_max[index],
        tempMin: data.daily.temperature_2m_min[index],
        precipitation: data.daily.precipitation_sum[index],
        weathercode: data.daily.weathercode[index]
      })) || [];
      
      this.selectedDayIndex = 0;
    });
}

Códigos meteorológicos:

  • 0: ☀️ Despejado
  • 1-3: 🌤️⛅☁️ Nublado
  • 45: 🌫️ Niebla
  • 51-65: 🌧️ Lluvia
  • 71-75: ❄️ Nieve
  • 95: ⛈️ Tormenta

6. Predicción de Mareas

Ubicación: src/app/services/tide.service.ts

getTideData(latitude: number, longitude: number, date: string): Observable<TideData[]> {
  const ports = this.portsSubject.getValue();
  const nearestPort = this.findNearestPort(latitude, longitude, ports);
  
  const formattedDate = date.replace(/-/g, '');
  const url = `${this.apiUrl}?request=gettide&format=json&id=${nearestPort.id}&date=${formattedDate}`;

  return this.http.get<TideResponse>(url).pipe(
    map(response => {
      const mareas = response.mareas?.datos?.marea || [];
      return mareas.map(marea => ({
        timestamp: new Date(`${response.mareas.fecha}T${marea.hora}:00Z`).toISOString(),
        height: parseFloat(marea.altura)
      })).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
    })
  );
}

Características:

  • Cálculo automático del puerto más cercano
  • Gráfico interactivo con Chart.js
  • Línea indicadora de hora actual
  • Actualización cada minuto
  • Datos del Instituto Hidrográfico de la Marina

🔌 Integraciones de API

Firebase Firestore

Colecciones:

// beaches: Información de playas
{
  id: string;
  name: string;
  slug: string;
  island: string;
  municipality: string;
  coverUrl: string;
  latitude: number;
  longitude: number;
  length: number;
  blueFlag: boolean;
  hasSand: boolean;
  hasRock: boolean;
  hasToilets: boolean;
  hasShowers: boolean;
  accessByCar: boolean;
  accessByFoot: string | null;
  annualMaxOccupancy: string | null;
  classification: string;
  environmentCondition: string | null;
  grade?: number;
  reviewsCount?: number;
}

// users: Información de usuarios
{
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  username: string;
  avatarUrl: string;
  createdAt: Date;
  favorites: { [beachId: string]: boolean };
}

// reviews: Comentarios y valoraciones
{
  id: number;
  beachId: string;
  userId: string;
  rating: number;
  comment: string;
  imageUrl?: string;
  createdAt: Date;
  updatedAt: Date;
}

Open-Meteo API

Endpoint: https://api.open-meteo.com/v1/forecast

GET /forecast
  ?latitude={lat}
  &longitude={lon}
  &daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode
  &timezone=auto

Respuesta:

{
  "daily": {
    "time": ["2025-01-28", "2025-01-29", ...],
    "temperature_2m_max": [22.5, 23.1, ...],
    "temperature_2m_min": [15.2, 16.0, ...],
    "precipitation_sum": [0, 2.3, ...],
    "weathercode": [0, 3, ...]
  }
}

Instituto Hidrográfico de la Marina

Endpoint: https://ideihm.covam.es/api-ihm/getmarea

GET /getmarea
  ?request=gettide
  &format=json
  &id={portId}
  &date={YYYYMMDD}

Respuesta:

{
  "mareas": {
    "puerto": "Las Palmas",
    "fecha": "2025-01-28",
    "datos": {
      "marea": [
        { "hora": "03:25", "altura": "2.45", "tipo": "pleamar" },
        { "hora": "09:50", "altura": "0.35", "tipo": "bajamar" }
      ]
    }
  }
}

🧩 Componentes Principales

BeachCard

Propósito: Tarjeta individual de playa con imagen, nombre, isla y distancia.

@Component({
  selector: 'app-beach-card',
  standalone: true,
  template: `
    <a [href]="'/beach/' + beach.slug" class="beach-card__link">
      <article class="beach-card">
        <figure class="beach-card__media">
          <img [src]="beach.coverUrl" [alt]="beach.name" />
          <div *ngIf="beach.blueFlag" class="beach-card__flag--blue">
            Bandera Azul
          </div>
        </figure>
        <div class="beach-card__details">
          <h3>{{ beach.name }}</h3>
          <p>{{ beach.island }}</p>
          <p *ngIf="distance">Estás a {{ distance }}</p>
        </div>
      </article>
    </a>
  `
})
export class BeachCardComponent implements OnInit {
  @Input() beach: Beach | null = null;
  distance: string | null = null;

  ngOnInit() {
    if (this.beach) this.getUserLocation();
  }
}

WeatherDisplay

Propósito: Mostrar pronóstico del tiempo para 7 días.

@Component({
  selector: 'app-weather-display',
  template: `
    <div class="weather-container">
      <!-- Día seleccionado -->
      <div *ngIf="weatherDays[selectedDayIndex]">
        <div class="weather-icon">
          {{ getWeatherIcon(weatherDays[selectedDayIndex].weathercode) }}
        </div>
        <p class="temperature">
          {{ weatherDays[selectedDayIndex].tempMax }}°C
        </p>
      </div>

      <!-- Selector de días -->
      <div class="days-bar">
        <button 
          *ngFor="let day of weatherDays; let i = index"
          (click)="selectDay(i)"
          [class.active]="i === selectedDayIndex">
          {{ day.date | date:'EEE' }}
        </button>
      </div>
    </div>
  `
})
export class WeatherDisplayComponent {
  @Input() latitude: number = 0;
  @Input() longitude: number = 0;
  weatherDays: DailyWeather[] = [];
  selectedDayIndex: number = 0;
}

TidesStatus

Propósito: Gráfico de mareas del día.

@Component({
  selector: 'app-tides-status',
  template: `
    <div class="tides-container">
      <canvas 
        *ngIf="!isLoading"
        baseChart
        [data]="lineChartData"
        [options]="lineChartOptions"
        [type]="lineChartType">
      </canvas>
    </div>
  `
})
export class TidesStatusComponent implements OnInit {
  @Input() beach!: Beach;
  
  lineChartData: ChartData<'line'> = {
    datasets: [{
      data: [],
      label: 'Altura de la marea (m)',
      borderColor: '#1e90ff'
    }]
  };
}

🔧 Servicios

AuthService

Responsabilidad: Gestión de autenticación con Firebase.

@Injectable({ providedIn: 'root' })
export class AuthService {
  async register(user: User) {
    const userCredential = await createUserWithEmailAndPassword(
      this._auth, user.email, user.password
    );
    
    await setDoc(doc(this._firestore, `Users/${userCredential.user.uid}`), {
      firstName: user.firstName,
      lastName: user.lastName,
      email: user.email,
      createdAt: new Date(),
      imageUrl: user.imageUrl || 'default-avatar.png'
    });
    
    return userCredential;
  }

  async login(email: string, password: string) {
    return await signInWithEmailAndPassword(this._auth, email, password);
  }

  async logout() {
    return await signOut(this._auth);
  }
}

BeachService

Responsabilidad: Acceso a datos de playas desde Firestore.

@Injectable({ providedIn: 'root' })
export class BeachService {
  constructor(private firestore: AngularFirestore) {}

  getAllBeaches(): Observable<Beach[]> {
    return this.firestore.collection<Beach>('beaches').valueChanges();
  }

  getBeachBySlug(slug: string): Observable<Beach | undefined> {
    return this.getAllBeaches().pipe(
      map(beaches => beaches.find(b => b.slug === slug))
    );
  }
}

FavouritesService

Responsabilidad: Gestión de playas favoritas del usuario.

@Injectable({ providedIn: 'root' })
export class FavouritesService {
  async addToFavourites(beachId: number): Promise<void> {
    const userDocRef = doc(this.firestore, `Users/${userId}`);
    await updateDoc(userDocRef, {
      [`favorites.${beachId}`]: true
    });
  }

  checkIfFavourite(beachId: number): Observable<boolean> {
    const userDocRef = doc(this.firestore, `Users/${userId}`);
    return from(getDoc(userDocRef)).pipe(
      map(docSnap => docSnap.data()?.['favorites']?.[beachId] === true)
    );
  }
}

ReviewService

Responsabilidad: CRUD de comentarios y reseñas.

@Injectable({ providedIn: 'root' })
export class ReviewService {
  createReview(formData: FormData): Observable<any> {
    return this.http.post(`${this.apiUrl}/reviews`, formData);
  }

  getReviewsForBeach(beachId: string): Observable<ReviewResponse> {
    return this.http.get<ReviewResponse>(`${this.apiUrl}/reviews/beach/${beachId}`);
  }

  updateReview(reviewId: string, formData: FormData): Observable<any> {
    return this.http.put(`${this.apiUrl}/reviews/${reviewId}`, formData);
  }

  deleteReview(reviewId: string): Observable<any> {
    return this.http.delete(`${this.apiUrl}/reviews/${reviewId}`);
  }
}

🚀 Despliegue

Despliegue en Vercel (Recomendado)

# 1. Instalar Vercel CLI
npm i -g vercel

# 2. Login
vercel login

# 3. Desplegar
vercel --prod

Configuración (vercel.json):

{
  "version": 2,
  "builds": [{
    "src": "package.json",
    "use": "@vercel/static-build",
    "config": { "distDir": "dist/playa/browser" }
  }],
  "routes": [
    { "handle": "filesystem" },
    { "src": "/(.*)", "dest": "/index.html" }
  ]
}

Despliegue en Firebase Hosting

# 1. Instalar Firebase CLI
npm install -g firebase-tools

# 2. Login
firebase login

# 3. Inicializar proyecto
firebase init hosting

# 4. Build y deploy
npm run build
firebase deploy --only hosting

Variables de Entorno en Producción

# Configurar en Vercel/Firebase
FIREBASE_API_KEY=your_api_key
FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
FIREBASE_PROJECT_ID=your_project_id
MAPTILER_API_KEY=your_maptiler_key

👥 Equipo de Desarrollo

Integrantes

  • Miguel Ángel Rodríguez Ruano - Frontend Developer
  • Gorka Eymard Santana Cabrera - Frontend Developer
  • Sergio Acosta Quintana - Frontend Developer

Roles y Responsabilidades

Desarrollador Responsabilidades Principales
Miguel Ángel Arquitectura, Firebase, Autenticación, Mapas
Gorka UI/UX, Componentes, Diseño Responsivo, Tailwind
Sergio Servicios, APIs Externas, Testing, Optimización

Metodología

  • Scrum con sprints de 2 semanas
  • Git Flow para control de versiones
  • Code Review obligatorio antes de merge
  • Figma para diseño y prototipos
  • Trello para gestión de tareas

🤝 Contribuir

Cómo Contribuir

  1. Fork el repositorio
  2. Crea una rama feature (git checkout -b feature/NuevaCaracteristica)
  3. Commit tus cambios (git commit -m 'Añadir nueva característica')
  4. Push a la rama (git push origin feature/NuevaCaracteristica)
  5. Abre un Pull Request

Guía de Estilo

// ✅ Buenas prácticas
// 1. Nombres descriptivos
const beachesWithBlueFlag = beaches.filter(b => b.blueFlag);

// 2. Tipos explícitos
function calculateDistance(lat1: number, lon1: number): string {
  // ...
}

// 3. Comentarios útiles
// Calcula la distancia usando la fórmula de Haversine
const distance = haversineDistance(userLat, beachLat);

// 4. Manejo de errores
this.beachService.getAllBeaches().subscribe({
  next: (beaches) => this.beaches = beaches,
  error: (err) => {
    console.error('Error loading beaches:', err);
    toast.error('No se pudieron cargar las playas');
  }
});

Convenciones de Nombres

  • Componentes: PascalCase (BeachCardComponent)
  • Servicios: PascalCase + Service (BeachService)
  • Variables: camelCase (beachList, selectedBeach)
  • Constantes: UPPER_SNAKE_CASE (API_BASE_URL)
  • Archivos: kebab-case (beach-card.component.ts)

Estructura de Commits

tipo(alcance): descripción breve

[cuerpo opcional]

[pie opcional]

Tipos:

  • feat: Nueva característica
  • fix: Corrección de bug
  • docs: Cambios en documentación
  • style: Formato, punto y coma, etc.
  • refactor: Refactorización de código
  • test: Añadir/modificar tests
  • chore: Tareas de mantenimiento

Ejemplo:

feat(beach-detail): añadir gráfico de mareas

- Integrar Chart.js para visualización
- Conectar con API del IHM
- Añadir indicador de hora actual

Closes #42

📄 Licencia

Este proyecto está bajo la licencia MIT.

MIT License

Copyright (c) 2025 Playea Team

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

📞 Contacto y Soporte


🎉 Agradecimientos

  • Open-Meteo por la API meteorológica gratuita
  • Instituto Hidrográfico de la Marina por los datos de mareas
  • MapTiler por las imágenes satelitales
  • Firebase por la infraestructura backend
  • Ionic Team por los componentes UI
  • Angular Team por el framework

🗺️ Roadmap Futuro

v2.0 (Q2 2025)

  • Sistema de notificaciones push
  • Modo offline con Service Workers
  • Compartir playas en redes sociales
  • Rutas y direcciones a playas
  • Chatbot de recomendaciones con IA

v2.1 (Q3 2025)

  • Aplicación móvil nativa (Capacitor)
  • Realidad aumentada para vistas 360°
  • Integración con calendarios
  • Sistema de badges y logros
  • API pública para desarrolladores

v3.0 (Q4 2025)

  • Marketplace de equipamiento de playa
  • Reserva de sombrillas/hamacas
  • Tours guiados virtuales
  • Integración con hoteles cercanos
  • Dashboard de analíticas avanzadas

Hecho con ❤️ por el equipo de Playea

🏖️ Visitar Playea · 📖 Documentación · 🐛 Reportar Bug

About

Canary Beaches Platform

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors