Actualiza la documentación para reflejar el cambio de Express a Fastify en el backend y ajusta las notas de la PoC en lecciones aprendidas.
This commit is contained in:
148
docs/api-integration.md
Normal file
148
docs/api-integration.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Integración de APIs externas — Prioridad y guía práctica
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Definir APIs prioritarias para el MVP, cómo obtener credenciales, ejemplos de uso y estrategias de robustez (rate limit, retries, fallback y normalización de datos).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## APIs priorizadas (MVP)
|
||||||
|
|
||||||
|
1. **IGDB (prioridad alta)**
|
||||||
|
2. **RAWG (prioridad alta)**
|
||||||
|
3. **TheGamesDB (prioridad media)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IGDB
|
||||||
|
|
||||||
|
- **Obtener credenciales**: registrar una app en Twitch Developer Console para obtener `CLIENT_ID` y `CLIENT_SECRET`. Obtener token con grant type `client_credentials` (POST a `https://id.twitch.tv/oauth2/token`).
|
||||||
|
|
||||||
|
- **Endpoints principales**: `POST https://api.igdb.com/v4/games` (consulta flexible via body con sintaxis IGDB), `POST https://api.igdb.com/v4/covers`, `POST https://api.igdb.com/v4/platforms`.
|
||||||
|
|
||||||
|
- **Ejemplo (buscar)**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Obtener token
|
||||||
|
curl -X POST 'https://id.twitch.tv/oauth2/token?client_id=$IGDB_CLIENT_ID&client_secret=$IGDB_CLIENT_SECRET&grant_type=client_credentials'
|
||||||
|
|
||||||
|
# Buscar juegos
|
||||||
|
curl -X POST 'https://api.igdb.com/v4/games' \
|
||||||
|
-H "Client-ID: $IGDB_CLIENT_ID" \
|
||||||
|
-H "Authorization: Bearer $IGDB_TOKEN" \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
--data 'fields id,name,first_release_date,platforms.name,genres.name,cover.url; search "zelda"; limit 5;'
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Respuesta (esquemática)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 12345,
|
||||||
|
"name": "Ejemplo",
|
||||||
|
"first_release_date": 1459468800,
|
||||||
|
"platforms": [{ "name": "Nintendo Switch" }],
|
||||||
|
"cover": { "url": "//images.igdb.com/...jpg" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Límites y manejo**: la API puede devolver `429` o cabeceras de límite; implementar retries exponenciales (ej. 3 intentos) y respetar `Retry-After`. Implementar circuit breaker si la API falla repetidamente.
|
||||||
|
- **Atribución**: mostrar origen de datos (ej. "Datos: IGDB") según términos del servicio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RAWG
|
||||||
|
|
||||||
|
- **Obtener credenciales**: registrarse en RAWG para obtener `RAWG_API_KEY` (https://rawg.io/apidocs).
|
||||||
|
- **Endpoints principales**: `GET https://api.rawg.io/api/games?key=API_KEY&search=...`, `GET https://api.rawg.io/api/games/{id}`.
|
||||||
|
- **Ejemplo**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl 'https://api.rawg.io/api/games?key=$RAWG_API_KEY&search=zelda&page_size=5'
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Respuesta (esquemática)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"count": 100,
|
||||||
|
"results": [
|
||||||
|
{ "id": 3498, "name": "GTA V", "released": "2013-09-17", "background_image": "https://..." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Límites y manejo**: RAWG suele tener límites por clave/plan; cachear y fallback a otros proveedores si falla.
|
||||||
|
- **Atribución**: revisar condiciones y mostrar HTTP o texto de fuente si es requerido por el proveedor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TheGamesDB
|
||||||
|
|
||||||
|
- **Obtener credenciales**: crear cuenta y generar API Key en https://thegamesdb.net.
|
||||||
|
- **Endpoints**: búsqueda por nombre y detalles (`/v1/Games/ByGameName?name=...`, `/v1/Games/ByGameID?id=...`).
|
||||||
|
- **Ejemplo**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H 'Authorization: Bearer $THEGAMESDB_KEY' 'https://api.thegamesdb.net/v1/Games/ByGameName?name=zelda'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estrategia de fallback y normalización
|
||||||
|
|
||||||
|
- **Orden de prioridad**: IGDB → RAWG → TheGamesDB (configurable).
|
||||||
|
- **Normalización (mapping)**:
|
||||||
|
- `title` ← `name`
|
||||||
|
- `platform` ← `platforms[].name`
|
||||||
|
- `release_date` ← `first_release_date` / `released` → convertir a ISO 8601
|
||||||
|
- `genres` ← `genres[].name`
|
||||||
|
- `cover_url` ← `cover.url` / `background_image`
|
||||||
|
- `external_ids` ← `{ igdb: id, rawg: id, thegamesdb: id }`
|
||||||
|
|
||||||
|
- **Fallback**: si IGDB no tiene portada, intentar RAWG; si falla, usar TheGamesDB. Registrar la fuente usada.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Caché y almacenamiento de artwork
|
||||||
|
|
||||||
|
- **Caché metadata**: LRU en memoria o Redis con TTL (por ejemplo 24h) para evitar sobrecargar APIs.
|
||||||
|
- **Almacenamiento de imágenes**: descargar y optimizar con `sharp` (crear versiones: thumb, medium), almacenar en `storage/artwork/{gameId}/cover.jpg` o S3.
|
||||||
|
- **Servicio proxy**: servir imágenes desde backend para no exponer keys ni URLs externas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manejo de errores y resiliencia
|
||||||
|
|
||||||
|
- Implementar **retries** exponenciales con jitter (3 intentos).
|
||||||
|
- Implementar **circuit breaker** para desconectar llamadas a un proveedor fuera de servicio por N minutos.
|
||||||
|
- Limitar concurrencia por proveedor (p. ej. 5 llamadas simultáneas) y usar colas para trabajos masivos (enriquecimiento masivo).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Variables de entorno (ejemplos)
|
||||||
|
|
||||||
|
```
|
||||||
|
IGDB_CLIENT_ID=...
|
||||||
|
IGDB_CLIENT_SECRET=...
|
||||||
|
RAWG_API_KEY=...
|
||||||
|
THEGAMESDB_API_KEY=...
|
||||||
|
EXTERNAL_API_CONCURRENCY=5
|
||||||
|
```
|
||||||
|
|
||||||
|
> Nota: **Nunca** exponer estas claves en el cliente; siempre pasar por el backend.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fuentes
|
||||||
|
|
||||||
|
- IGDB API docs, RAWG API docs, TheGamesDB API docs.
|
||||||
|
- Patrones: retries, circuit breakers (ej. libraries: `p-retry`, `cockatiel`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Metadatos**
|
||||||
|
Autor: Quasar (investigación automatizada)
|
||||||
|
Última actualización: 2026-02-07
|
||||||
115
docs/architecture.md
Normal file
115
docs/architecture.md
Normal 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
|
||||||
171
docs/data-model.md
Normal file
171
docs/data-model.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Modelo de datos — Entidades y esquema sugerido
|
||||||
|
|
||||||
|
## Diagrama ER (texto)
|
||||||
|
|
||||||
|
Game ↔ RomFile (1‑N)
|
||||||
|
Game ↔ Artwork (1‑N)
|
||||||
|
Game ↔ Platform (N‑N)
|
||||||
|
Game ↔ Tag (N‑N)
|
||||||
|
Game ↔ Purchase (1‑N)
|
||||||
|
Game ↔ PriceHistory (1‑N)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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` (Json?) — campos flexibles.
|
||||||
|
- `createdAt`, `updatedAt`.
|
||||||
|
|
||||||
|
### Platform
|
||||||
|
|
||||||
|
- `id`, `name`, `slug`, `generation`.
|
||||||
|
|
||||||
|
### RomFile
|
||||||
|
|
||||||
|
- `id`, `path`, `filename`, `checksum` (unique), `size` (int), `format`, `hashes` (Json opcional), `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 = "postgresql" // para dev puede usarse sqlite
|
||||||
|
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 Json?
|
||||||
|
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 Json?
|
||||||
|
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 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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -43,9 +43,9 @@
|
|||||||
|
|
||||||
### PoC propuesta
|
### PoC propuesta
|
||||||
|
|
||||||
PoC propuesta: Backend mínimo (Node/Express) que implemente search IGDB + RAWG y cache LRU de 24h
|
PoC propuesta: Backend mínimo (Node/Fastify) que implemente search IGDB + RAWG y cache LRU de 24h
|
||||||
|
|
||||||
- Backend mínimo: Node/Express (JavaScript o TypeScript) con endpoints REST simples.
|
- Backend mínimo: Node/Fastify (JavaScript o TypeScript) con endpoints REST simples.
|
||||||
- Endpoint primario: `GET /api/search?query=<término>` que combine resultados de IGDB (primario) y RAWG (fallback) y normalice campos.
|
- Endpoint primario: `GET /api/search?query=<término>` que combine resultados de IGDB (primario) y RAWG (fallback) y normalice campos.
|
||||||
- Cache: LRU en memoria con TTL 24h (ej. `node-lru-cache`) y opción a Redis con TTL para producción.
|
- Cache: LRU en memoria con TTL 24h (ej. `node-lru-cache`) y opción a Redis con TTL para producción.
|
||||||
- Control de uso: contadores por API y por usuario/IP, circuit breaker y backoff (respetar 429 y header `Retry-After`).
|
- Control de uso: contadores por API y por usuario/IP, circuit breaker y backoff (respetar 429 y header `Retry-After`).
|
||||||
|
|||||||
89
docs/requirements.md
Normal file
89
docs/requirements.md
Normal 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 **self‑hosted** 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
## Plan: Gestor de biblioteca de videojuegos y ROMs (Quasar)
|
## Plan: Gestor de biblioteca de videojuegos y ROMs (Quasar)
|
||||||
|
|
||||||
Aplicación web self-hosted para gestionar una biblioteca de ROMs y videojuegos físicos/digitales. Permite escanear directorios de ROMs, enriquecer metadatos vía APIs públicas (IGDB, RAWG, TheGamesDB), y registrar manualmente juegos físicos/digitales con precio, condición y notas. Stack: TypeScript + React + Vite + shadcn/ui (frontend), Node.js + Express + SQLite (backend).
|
Aplicación web self-hosted para gestionar una biblioteca de ROMs y videojuegos físicos/digitales. Permite escanear directorios de ROMs, enriquecer metadatos vía APIs públicas (IGDB, RAWG, TheGamesDB), y registrar manualmente juegos físicos/digitales con precio, condición y notas. Stack: TypeScript + React + Vite + shadcn/ui (frontend), Node.js + Fastify + TypeScript + Prisma + SQLite (backend).
|
||||||
|
|
||||||
**Fases: 9**
|
**Fases: 9**
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Aplicación web self-hosted para gestionar una biblioteca de ROMs y videojuegos
|
|||||||
|
|
||||||
### **Fase 2: Requisitos y diseño técnico**
|
### **Fase 2: Requisitos y diseño técnico**
|
||||||
|
|
||||||
- **Objetivo:** Definir arquitectura (monorepo o separado), estructura de carpetas, stack definitivo (Express/Fastify, Prisma/TypeORM, SQLite), APIs a integrar (IGDB, RAWG, TheGamesDB), y documento de modelo de datos inicial.
|
- **Objetivo:** Definir arquitectura (monorepo o separado), estructura de carpetas, stack definitivo (Fastify + Prisma, SQLite), APIs a integrar (IGDB, RAWG, TheGamesDB), y documento de modelo de datos inicial.
|
||||||
- **Archivos/Funciones a crear/modificar:**
|
- **Archivos/Funciones a crear/modificar:**
|
||||||
- `docs/requirements.md` — requisitos funcionales y no funcionales
|
- `docs/requirements.md` — requisitos funcionales y no funcionales
|
||||||
- `docs/architecture.md` — decisiones arquitectónicas (monorepo vs multi-repo, API REST structure)
|
- `docs/architecture.md` — decisiones arquitectónicas (monorepo vs multi-repo, API REST structure)
|
||||||
@@ -37,11 +37,11 @@ Aplicación web self-hosted para gestionar una biblioteca de ROMs y videojuegos
|
|||||||
|
|
||||||
### **Fase 3: Backend base y modelo de datos**
|
### **Fase 3: Backend base y modelo de datos**
|
||||||
|
|
||||||
- **Objetivo:** Configurar backend (Express + TypeScript + Prisma + SQLite), definir schema de BD (Game, RomFile, Platform, Purchase, Artwork), migraciones y seeders básicos.
|
- **Objetivo:** Configurar backend (Fastify + TypeScript + Prisma + SQLite), definir schema de BD (Game, RomFile, Platform, Purchase, Artwork), migraciones y seeders básicos.
|
||||||
- **Archivos/Funciones a crear/modificar:**
|
- **Archivos/Funciones a crear/modificar:**
|
||||||
- `backend/package.json` — dependencias (express, prisma, cors, dotenv, etc.)
|
- `backend/package.json` — dependencias (fastify, prisma, @fastify/cors, dotenv, etc.)
|
||||||
- `backend/tsconfig.json` — configuración TypeScript backend
|
- `backend/tsconfig.json` — configuración TypeScript backend
|
||||||
- `backend/src/index.ts` — servidor Express inicial
|
- `backend/src/index.ts` — servidor Fastify inicial
|
||||||
- `backend/prisma/schema.prisma` — modelos (Game, RomFile, Platform, Purchase, Artwork)
|
- `backend/prisma/schema.prisma` — modelos (Game, RomFile, Platform, Purchase, Artwork)
|
||||||
- `backend/prisma/migrations/` — migraciones Prisma
|
- `backend/prisma/migrations/` — migraciones Prisma
|
||||||
- `backend/src/routes/healthcheck.ts` — endpoint `/api/health`
|
- `backend/src/routes/healthcheck.ts` — endpoint `/api/health`
|
||||||
@@ -51,7 +51,7 @@ Aplicación web self-hosted para gestionar una biblioteca de ROMs y videojuegos
|
|||||||
- `backend/tests/models/romFile.spec.ts` — validaciones del modelo RomFile
|
- `backend/tests/models/romFile.spec.ts` — validaciones del modelo RomFile
|
||||||
- **Pasos:**
|
- **Pasos:**
|
||||||
1. Escribir tests que fallen (healthcheck endpoint, crear modelo Game y validar)
|
1. Escribir tests que fallen (healthcheck endpoint, crear modelo Game y validar)
|
||||||
2. Configurar Express + Prisma, definir schema, ejecutar migración
|
2. Configurar Fastify + Prisma, definir schema, ejecutar migración
|
||||||
3. Implementar endpoint `/api/health`
|
3. Implementar endpoint `/api/health`
|
||||||
4. Ejecutar tests y verificar que pasan
|
4. Ejecutar tests y verificar que pasan
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ Aplicación web self-hosted para gestionar una biblioteca de ROMs y videojuegos
|
|||||||
## **Decisiones técnicas clave** 🔧
|
## **Decisiones técnicas clave** 🔧
|
||||||
|
|
||||||
- **Monorepo:** `/backend` y `/frontend` en el mismo repo, con workspaces de Yarn
|
- **Monorepo:** `/backend` y `/frontend` en el mismo repo, con workspaces de Yarn
|
||||||
- **Backend:** Node.js + Express + TypeScript + Prisma + SQLite (migration a PostgreSQL posible en futuro)
|
- **Backend:** Node.js + Fastify + TypeScript + Prisma + SQLite (migration a PostgreSQL posible en futuro)
|
||||||
- **Frontend:** React + Vite + TypeScript + Tailwind CSS + shadcn/ui + TanStack Query + TanStack Router
|
- **Frontend:** React + Vite + TypeScript + Tailwind CSS + shadcn/ui + TanStack Query + TanStack Router
|
||||||
- **APIs de metadata:** IGDB (primary), RAWG (fallback), TheGamesDB (artwork/retro)
|
- **APIs de metadata:** IGDB (primary), RAWG (fallback), TheGamesDB (artwork/retro)
|
||||||
- **Tests:** Backend (Vitest + Supertest), Frontend (Vitest + React Testing Library), E2E (Playwright)
|
- **Tests:** Backend (Vitest + Supertest), Frontend (Vitest + React Testing Library), E2E (Playwright)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ test.describe('Docs content checks', () => {
|
|||||||
test('lessons-learned contains PoC and license compatibility note', async () => {
|
test('lessons-learned contains PoC and license compatibility note', async () => {
|
||||||
const file = await fs.readFile(path.join(docsDir, 'lessons-learned.md'), 'utf-8');
|
const file = await fs.readFile(path.join(docsDir, 'lessons-learned.md'), 'utf-8');
|
||||||
expect(file).toContain(
|
expect(file).toContain(
|
||||||
'PoC propuesta: Backend mínimo (Node/Express) que implemente search IGDB + RAWG y cache LRU de 24h'
|
'PoC propuesta: Backend mínimo (Node/Fastify) que implemente search IGDB + RAWG y cache LRU de 24h'
|
||||||
);
|
);
|
||||||
expect(file).toContain('compatibilidad de licencias');
|
expect(file).toContain('compatibilidad de licencias');
|
||||||
expect(file).toContain('Fuentes');
|
expect(file).toContain('Fuentes');
|
||||||
|
|||||||
Reference in New Issue
Block a user