Refactor code structure for improved readability and maintainability
Some checks failed
CI / lint (push) Failing after 1m4s
CI / test-backend (push) Has been skipped
CI / test-frontend (push) Has been skipped
CI / test-e2e (push) Has been skipped

This commit is contained in:
2026-02-22 18:18:46 +01:00
parent c27e9bec7a
commit 0c9c408564
98 changed files with 8207 additions and 5250 deletions

View File

@@ -0,0 +1,115 @@
# Arquitectura — Diseño técnico de alto nivel
## Visión general
Arquitectura pensada para un **MVP local-first / self-hosted** que pueda evolucionar a un servicio multiusuario. Propuesta: **monorepo** con dos paquetes principales: `/backend` y `/frontend` (Yarn workspaces). Backend expone una REST API (Fastify + TypeScript); Frontend es una SPA en React (Vite o similar) usando **TanStack Query** y **TanStack Router**.
## Diagrama de alto nivel (texto)
```
Usuario (Browser React) ↔ Frontend (TanStack Query/Router) ↔ Backend REST (Fastify + TypeScript)
Worker (Bull/BullMQ) ↔ Redis (cache/cola, opcional)
DB (SQLite dev → Postgres prod) + Storage (local FS | S3)
APIs externas (IGDB, RAWG, TheGamesDB)
```
---
## Decisiones clave y justificación
- **Monorepo (Yarn workspaces)**: coherencia de versiones, compartir utilidades y configuración (lint/tsconfig).
- **Fastify + TypeScript** (REST): recomendado para rendimiento y ergonomía con TypeScript; ofrece sistema de plugins encapsulados, validación/serialización basada en Ajv integrada, logging muy rápido (pino) y mejor throughput en producción.
- **Prisma** como ORM: productivo y portátil entre SQLite/Postgres.
- **SQLite en dev / Postgres en producción**: facilidad de desarrollo y migraciones seguras para futuro.
- **Bull/BullMQ + Redis** para trabajos asíncronos (escaneo, enrich, procesamiento de imágenes).
- **Storage**: Adapter pattern para `StorageAdapter` que permita usar S3 o disco local según despliegue.
---
## Stack propuesto (resumen)
- Backend: Node.js, TypeScript, Fastify, Prisma, Bull/BullMQ, Redis (opcional), Sharp (procesado de imágenes), @fastify/multipart (uploads), @fastify/helmet, @fastify/rate-limit, Ajv (validación), pino (logging).
- Frontend: React, TypeScript (opcional), Vite, TanStack Query, TanStack Router, Tailwind CSS (opcional).
- Infra / DevOps: Docker Compose (self-hosting), GitHub Actions (CI), env vars + GitHub Secrets.
---
## Estructura de carpetas sugerida
```
/ (monorepo)
package.json (workspaces)
/backend
package.json
prisma/schema.prisma
src/
index.ts
controllers/
services/
routes/
jobs/
workers/
lib/
/frontend
package.json
src/
routes/
components/
hooks/
lib/
assets/
/docs
```
---
## Scripts útiles sugeridos (`package.json` raíz)
```json
{
"scripts": {
"dev": "concurrently \"yarn dev:backend\" \"yarn dev:frontend\"",
"dev:backend": "cd backend && ts-node-dev --respawn src/index.ts",
"dev:frontend": "cd frontend && vite",
"start": "node backend/dist/index.js",
"lint": "eslint . --ext .ts,.tsx,.js",
"format": "prettier --write .",
"prisma:migrate": "cd backend && prisma migrate dev",
"prisma:studio": "cd backend && prisma studio"
}
}
```
---
## Seguridad y operacionales
- Variables sensibles en `.env` y en GitHub Secrets para CI.
- CORS configurado para orígenes permitidos; validación y saneamiento de entradas.
- Rate limiting global y por endpoint (enriquecimiento de metadatos).
- Backups: snapshots regulares de la DB y copia periódica de carpeta `artwork/`.
- Despliegue: `docker-compose.yml` con volúmenes para DB y assets; salud (`/health`) y readiness probes.
- Observabilidad: logs estructurados (pino/winston); endpoint de métricas básico (Prometheus) o integraciones simples.
---
## Integración de APIs externas
- Encapsular cada proveedor en un adaptador (ej.: `IgdbClient`, `RawgClient`) con interfaz común: `search(query)`, `getById(id)`, `getArtwork(id)`.
- Caching: Redis o LRU en memoria con TTL (ej. 24h) para respuestas y metadatos de artwork.
- Retries y protección: reconectar con backoff + circuit breaker para evitar fallos en cascada.
---
## Fuentes
- Documentación: Fastify, Prisma, Bull/BullMQ, Redis, Sharp, TanStack Query/Router.
---
**Metadatos**
Autor: Quasar (investigación automatizada)
Última actualización: 2026-02-07

View File

@@ -0,0 +1,181 @@
# Modelo de datos — Entidades y esquema sugerido
## Diagrama ER (texto)
Game ↔ RomFile (1N)
Game ↔ Artwork (1N)
Game ↔ Platform (NN)
Game ↔ Tag (NN)
Game ↔ Purchase (1N)
Game ↔ PriceHistory (1N)
---
## Entidades principales (campos sugeridos)
### Game
- `id` (cuid) — identificador.
- `title` (string) — nombre principal.
- `slug` (string) — único, legible.
- `description` (text) — opcional.
- `releaseDate` (DateTime?) — fecha principal.
- `igdbId`, `rawgId`, `thegamesdbId` (Int?) — ids externos.
- `extra` (String?) — JSON serializado (compatible con SQLite); usar `JSON.parse`/`JSON.stringify` al leer/escribir.
- `createdAt`, `updatedAt`.
### Platform
- `id`, `name`, `slug`, `generation`.
### RomFile
- `id`, `path`, `filename`, `checksum` (unique), `size` (int), `format`, `hashes` (String? — JSON serializado, ej.: {"crc32":"...","md5":"..."}), `status` (active/missing), `addedAt`, `lastSeenAt`, `gameId`.
### Artwork
- `id`, `gameId`, `type` (cover/screenshot), `sourceUrl`, `localPath`, `width`, `height`, `fetchedAt`.
### Purchase
- `id`, `gameId`, `priceCents` (int), `currency`, `store`, `date`, `receiptPath`.
### PriceHistory
- `id`, `gameId`, `priceCents`, `currency`, `recordedAt`, `source`.
### Tag
- `id`, `name` (unique), color.
### User (opcional)
- `id`, `name`, `email` (unique), `passwordHash`, `role`.
---
## Índices y constraints recomendados
- `RomFile.checksum`**UNIQUE** (detectar duplicados).
- `Game`: índice por `title` y `slug` (`slug` único).
- Composite unique sugerido: `[primaryPlatformId, title, releaseDate]` para evitar duplicados de una misma versión/plataforma.
- Índices para búsquedas frecuentes (title, platform, tags).
---
## Fragmento `schema.prisma` (ejemplo)
```prisma
datasource db {
provider = "sqlite" // usar SQLite para desarrollo; cambiar a postgresql en producción si se necesita JSON nativo
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Game {
id String @id @default(cuid())
title String
slug String @unique
description String?
releaseDate DateTime?
igdbId Int? @unique
rawgId Int? @unique
thegamesdbId Int? @unique
platforms Platform[]
romFiles RomFile[]
artworks Artwork[]
tags Tag[]
extra String? // JSON serializado (usar JSON.parse/JSON.stringify para leer y escribir)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([title])
@@unique([slug])
}
model Platform {
id String @id @default(cuid())
name String @unique
slug String @unique
games Game[]
}
model RomFile {
id String @id @default(cuid())
path String
filename String
checksum String @unique
size Int
format String
hashes String? // JSON serializado (ej.: {"crc32":"...","md5":"..."})
game Game? @relation(fields: [gameId], references: [id])
gameId String?
addedAt DateTime @default(now())
lastSeenAt DateTime?
status String @default("active")
@@index([checksum])
}
model Artwork {
id String @id @default(cuid())
game Game @relation(fields: [gameId], references: [id])
gameId String
type String
sourceUrl String
localPath String?
width Int?
height Int?
fetchedAt DateTime @default(now())
}
model Purchase {
id String @id @default(cuid())
game Game @relation(fields: [gameId], references: [id])
gameId String
priceCents Int
currency String
store String?
date DateTime
receiptPath String?
}
model PriceHistory {
id String @id @default(cuid())
game Game @relation(fields: [gameId], references: [id])
gameId String
priceCents Int
currency String
recordedAt DateTime @default(now())
source String?
}
model Tag {
id String @id @default(cuid())
name String @unique
games Game[]
}
```
> Nota: para monedas se recomienda almacenar `priceCents` (int) para compatibilidad; si se usa Postgres se puede optar por `Decimal`. Nota: SQLite no soporta tipos JSON nativos; en este ejemplo se usan `String` para JSON serializado. Si migras a Postgres, puedes usar `Json`/`JsonB` para campos flexibles.
---
## Migraciones y evolución del esquema
- Usar **Prisma Migrate** para versionar cambios.
- Preferir migraciones no destructivas (añadir columnas `NULL` y rellenarlas en background).
- Mantener seeds de desarrollo para pruebas manuales y reproducibles.
---
## Fuentes
- Prisma schema conventions, patterns para modelado relacional y técnicas de deduplicación de archivos.
---
**Metadatos**
Autor: Quasar (investigación automatizada)
Última actualización: 2026-02-07

View File

@@ -0,0 +1,89 @@
# Requisitos — Fase 2: Requisitos y criterios de aceptación
## Resumen breve del propósito (MVP)
Quasar es una aplicación **local-first** y **selfhosted** para gestionar una colección personal de videojuegos: escanear ROMs locales, registrar juegos físicos y digitales, enriquecer metadata (portadas, géneros, fechas) desde APIs públicas, y permitir exportación/importación de la colección. El MVP debe ser utilizable en un entorno de una única máquina/usuario con opción de evolucionar a despliegues multiusuario.
---
## Requisitos funcionales (priorizados)
| # | Requisito | Prioridad | Criterio mínimo de aceptación |
| --- | ------------------------------------------------- | --------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Escaneo e importación de ROMs locales (recursivo) | **P0** | Detecta archivos en directorio configurado, calcula checksum y crea entrada `RomFile` con metadata básica (nombre, tamaño, ruta, checksum). |
| 2 | CRUD de juegos y vinculación de ROMs | **P0** | Crear/editar/eliminar juegos; vincular/desvincular archivos ROM a un juego. |
| 3 | Enriquecimiento de metadata (IGDB / RAWG) | **P0** | Buscar por título y aplicar metadata seleccionada (cover, géneros, fecha). |
| 4 | Gestión de artwork local | **P0** | Descargar portada al almacenamiento local optimizado y servirla desde el backend. |
| 5 | Exportar/Importar (CSV/JSON) | **P0** | Exportar la lista de juegos con campos clave; importar (básico) para recuperación. |
| 6 | Registro de compras y precios | **P1** | Añadir registro de compra con precio, moneda, fecha y opcionalmente recibo. |
| 7 | Dedupe y verificación por checksum | **P1** | Detectar duplicados por checksum y ofrecer opciones (fusionar, ignorar). |
| 8 | Filtrado, búsqueda y etiquetado | **P1** | Búsqueda por título, plataforma, etiquetas y filtros básicos. |
| 9 | Backups y restauración básicos | **P1** | Exportar DB y assets; restaurar desde exportación. |
| 10 | Configuración de carpetas y preferencias | **P2** | Interfaz para configurar rutas de escaneo, límites y opciones de importación. |
---
## Requisitos no funcionales
- **Seguridad**: No almacenar credenciales en el repositorio; variables sensibles en `.env` y GitHub Secrets en CI; saneamiento y validación de inputs en backend.
- **Privacidad**: Local-first por defecto; telemetría deshabilitada.
- **Rendimiento**: Escaneos y enriquecimiento deben ejecutarse como trabajos asíncronos; hashing con streaming para archivos grandes.
- **Escalabilidad y portabilidad**: Arquitectura preparada para migrar de SQLite (dev/local) a Postgres (producción).
- **Disponibilidad**: Backups regulares y endpoint de salud; tolerar fallos de APIs externas con retries y cola.
---
## Casos de uso principales y flujos de usuario
### 1) Importar ROMs (escaneo automático)
1. Usuario configura ruta(s) de escaneo.
2. Backend inicia job de escaneo (worker).
3. Para cada archivo: calcular checksum, extraer tamaño/fecha, crear `RomFile`.
4. Si hay duplicados por checksum, marcar y notificar al usuario.
5. Ofrecer emparejado automático con juegos existentes o sugerir búsqueda externa.
### 2) Crear juego manual
1. Usuario abre formulario "Nuevo juego".
2. Rellenar título, plataforma, fecha, tags, descripción.
3. Guardar y opcionalmente vincular ROM(s) existentes.
### 3) Buscar / enriquecer metadata
1. Usuario busca por título en la UI -> backend consulta IGDB/RAWG.
2. Mostrar resultados con mini-previsualización (cover, plataformas, fecha).
3. Usuario selecciona resultado y aplica metadata al registro local.
### 4) Registrar compra
1. Desde la ficha del juego, usuario añade una compra (precio, moneda, tienda, fecha).
2. Opcionalmente adjunta imagen del recibo.
### 5) Exportar CSV / JSON
1. Usuario elige formato y filtros (plataforma, tags).
2. Backend genera el archivo y lo descarga.
---
## Criterios de aceptación del MVP (ejemplos testables)
- Escanear un directorio y listar ROMs con nombre, tamaño y checksum.
- Crear/leer/editar/eliminar un juego y vincular al menos un `RomFile`.
- Buscar en una API externa y aplicar metadata a un juego.
- Descargar y servir una portada optimizada localmente.
- Exportar colección en CSV y JSON.
---
## Fuentes
- IGDB, RAWG, TheGamesDB (documentación pública).
- Buenas prácticas: Documentación de Prisma, Fastify, React y TanStack Query/Router.
---
**Metadatos**
Autor: Quasar (investigación automatizada)
Última actualización: 2026-02-07