Descripción General

El diagrama de clases refleja la Arquitectura Hexagonal adoptada para el proyecto, organizando las clases en tres capas principales:

  1. Capa de Dominio: Entidades del negocio, Value Objects y Puertos (interfaces)
  2. Capa de Aplicación: Casos de uso y servicios que orquestan la lógica
  3. Capa de Infraestructura: Adaptadores que implementan los puertos (Controllers, Repositories, Spotify Adapter)

Diagrama de Clases (Backend)

<aside> <img src="/icons/subtask_gray.svg" alt="/icons/subtask_gray.svg" width="40px" />

Mermaid Chart - Backend

</aside>

---
config:
  layout: elk
  theme: neutral
---
classDiagram
    %% ============================================
    %% CAPA DE DOMINIO - ENTIDADES
    %% ============================================

    class User {
        -UUID id
        -String spotifyId
        -String username
        -String email
        -String profilePictureUrl
        -String spotifyAccessToken
        -String spotifyRefreshToken
        -LocalDateTime tokenExpiresAt
        -LocalDateTime createdAt
        -LocalDateTime updatedAt
        +getId() UUID
        +getSpotifyId() String
        +getUsername() String
        +isTokenExpired() boolean
        +updateTokens(accessToken, refreshToken, expiresIn) void
    }

    class Song {
        -UUID id
        -String spotifyId
        -String title
        -String artist
        -String album
        -String coverUrl
        -String previewUrl
        -Integer durationMs
        -Genre genre
        -Integer popularity
        -String releaseYear
        +getId() UUID
        +getSpotifyId() String
        +getTitle() String
        +getArtist() String
        +getFullTitle() String
    }

    class Swipe {
        -UUID id
        -User user
        -Song song
        -SwipeDirection direction
        -LocalDateTime swipedAt
        -boolean syncedToSpotify
        +getId() UUID
        +isLike() boolean
        +isDislike() boolean
        +markAsSynced() void
    }

    class Playlist {
        -UUID id
        -String spotifyPlaylistId
        -User owner
        -String name
        -String description
        -boolean isPublic
        -boolean isDefault
        -List~Song~ songs
        -LocalDateTime createdAt
        -LocalDateTime updatedAt
        +getId() UUID
        +getSpotifyPlaylistId() String
        +isDefault() boolean
        +addSong(song) void
        +removeSong(song) void
        +getSongCount() int
    }

    %% ============================================
    %% CAPA DE DOMINIO - VALUE OBJECTS
    %% ============================================

    class SwipeDirection {
        <<enumeration>>
        LIKE
        DISLIKE
    }

    class Genre {
        <<enumeration>>
        POP
        ROCK
        HIPHOP
        ELECTRONIC
        JAZZ
        CLASSICAL
        REGGAETON
        INDIE
        OTHER
    }

    class SwipeFilter {
        -List~Genre~ genres
        -String mood
        -String decade
        -Integer minPopularity
        -Integer maxPopularity
        +isEmpty() boolean
        +matches(song) boolean
    }

    %% ============================================
    %% CAPA DE DOMINIO - PUERTOS (INTERFACES)
    %% ============================================

    class UserRepository {
        <<interface>>
        +save(user) User
        +findById(id) Optional~User~
        +findBySpotifyId(spotifyId) Optional~User~
        +existsBySpotifyId(spotifyId) boolean
    }

    class SongRepository {
        <<interface>>
        +save(song) Song
        +findById(id) Optional~Song~
        +findBySpotifyId(spotifyId) Optional~Song~
        +findByFilters(filters) List~Song~
    }

    class SwipeRepository {
        <<interface>>
        +save(swipe) Swipe
        +findByUserId(userId) List~Swipe~
        +findPendingSync(userId) List~Swipe~
        +countLikesByUserId(userId) Long
    }

    class PlaylistRepository {
        <<interface>>
        +save(playlist) Playlist
        +findById(id) Optional~Playlist~
        +findAllByUserId(userId) List~Playlist~
        +findDefaultByUserId(userId) Optional~Playlist~
        +findBySpotifyPlaylistId(playlistId) Optional~Playlist~
    }

    class SpotifyMusicProvider {
        <<interface>>
        +authenticateUser(authCode) SpotifyAuthResponse
        +refreshAccessToken(refreshToken) SpotifyAuthResponse
        +getUserProfile(accessToken) SpotifyUserProfile
        +createPlaylist(userId, name) SpotifyPlaylist
        +addSongToPlaylist(playlistId, songId) boolean
        +getRecommendations(filters) List~Song~
        +getSongById(songId) Optional~Song~
    }

    %% ============================================
    %% CAPA DE APLICACIÓN - SERVICIOS / USE CASES
    %% ============================================

    class AuthenticationService {
        -UserRepository userRepository
        -SpotifyMusicProvider spotifyProvider
        -PlaylistRepository playlistRepository
        +authenticateWithSpotify(authCode) AuthenticationResult
        +refreshUserToken(userId) void
        +disconnectSpotify(userId) void
    }

    class SwipeService {
        -SwipeRepository swipeRepository
        -SongRepository songRepository
        -PlaylistService playlistService
        -UserRepository userRepository
        +recordSwipe(userId, songId, direction) Swipe
        +getUserSwipes(userId) List~Swipe~
        +syncPendingSwipes(userId) void
    }

    class PlaylistService {
        -PlaylistRepository playlistRepository
        -SpotifyMusicProvider spotifyProvider
        -UserRepository userRepository
        +createDefaultPlaylist(userId) Playlist
        +createCustomPlaylist(userId, name, isPublic) Playlist
        +addSongToPlaylist(playlistId, songId) void
        +addSongToDefaultPlaylist(userId, songId) void
        +getUserPlaylists(userId) List~Playlist~
        +getDefaultPlaylist(userId) Optional~Playlist~
        +updatePlaylistSettings(playlistId, name, isPublic) void
        +deletePlaylist(playlistId) void
    }

    class RecommendationService {
        -SongRepository songRepository
        -SpotifyMusicProvider spotifyProvider
        -SwipeRepository swipeRepository
        +getRecommendations(userId, filters) List~Song~
        +getNextSong(userId) Song
    }

    %% ============================================
    %% CAPA DE INFRAESTRUCTURA - ADAPTADORES
    %% ============================================

    class SpotifyAdapter {
        <<adapter>>
        -RestTemplate restTemplate
        -String clientId
        -String clientSecret
        -int maxRetries
        +authenticateUser(authCode) SpotifyAuthResponse
        +refreshAccessToken(refreshToken) SpotifyAuthResponse
        +getUserProfile(accessToken) SpotifyUserProfile
        +createPlaylist(userId, name) SpotifyPlaylist
        +addSongToPlaylist(playlistId, songId) boolean
        +getRecommendations(filters) List~Song~
        -executeWithRetry(request) Response
    }

    class UserController {
        <<controller>>
        -AuthenticationService authService
        +authenticate(authCode) ResponseEntity
        +getProfile(userId) ResponseEntity
        +disconnect(userId) ResponseEntity
    }

    class SwipeController {
        <<controller>>
        -SwipeService swipeService
        -RecommendationService recommendationService
        +swipeSong(userId, songId, direction) ResponseEntity
        +getSwipeHistory(userId) ResponseEntity
        +getNextSong(userId) ResponseEntity
    }

    class PlaylistController {
        <<controller>>
        -PlaylistService playlistService
        +getUserPlaylists(userId) ResponseEntity
        +getDefaultPlaylist(userId) ResponseEntity
        +createPlaylist(userId, request) ResponseEntity
        +updatePlaylist(playlistId, request) ResponseEntity
        +deletePlaylist(playlistId) ResponseEntity
    }

    %% ============================================
    %% RELACIONES - DOMINIO
    %% ============================================

    User "1" --> "*" Swipe : realiza
    Song "1" --> "*" Swipe : es evaluada en
    Swipe --> SwipeDirection : tiene
    Song --> Genre : pertenece a

    User "1" --> "*" Playlist : posee
    Playlist "1" --> "*" Song : contiene

    %% ============================================
    %% RELACIONES - SERVICIOS CON PUERTOS
    %% ============================================

    AuthenticationService --> UserRepository : usa
    AuthenticationService --> SpotifyMusicProvider : usa
    AuthenticationService --> PlaylistRepository : usa

    SwipeService --> SwipeRepository : usa
    SwipeService --> SongRepository : usa
    SwipeService --> UserRepository : usa
    SwipeService --> PlaylistService : usa

    PlaylistService --> PlaylistRepository : usa
    PlaylistService --> SpotifyMusicProvider : usa
    PlaylistService --> UserRepository : usa

    RecommendationService --> SongRepository : usa
    RecommendationService --> SpotifyMusicProvider : usa
    RecommendationService --> SwipeRepository : usa

    %% ============================================
    %% RELACIONES - ADAPTADORES IMPLEMENTAN PUERTOS
    %% ============================================

    SpotifyAdapter ..|> SpotifyMusicProvider : implementa

    %% ============================================
    %% RELACIONES - CONTROLLERS USAN SERVICIOS
    %% ============================================

    UserController --> AuthenticationService : usa
    SwipeController --> SwipeService : usa
    SwipeController --> RecommendationService : usa
    PlaylistController --> PlaylistService : usa


Explicación del Diagrama

1. Capa de Dominio (Core del negocio)

Entidades principales:

Value Objects: