diff --git a/docs/api-integration.md b/docs/api-integration.md new file mode 100644 index 0000000..749d5fe --- /dev/null +++ b/docs/api-integration.md @@ -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 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..64bd3f0 --- /dev/null +++ b/docs/architecture.md @@ -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 diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 0000000..803d1db --- /dev/null +++ b/docs/data-model.md @@ -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 diff --git a/docs/lessons-learned.md b/docs/lessons-learned.md index 2dceb8b..4f5b9f9 100644 --- a/docs/lessons-learned.md +++ b/docs/lessons-learned.md @@ -43,9 +43,9 @@ ### 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=` 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. - Control de uso: contadores por API y por usuario/IP, circuit breaker y backoff (respetar 429 y header `Retry-After`). diff --git a/docs/requirements.md b/docs/requirements.md new file mode 100644 index 0000000..ca1a1e0 --- /dev/null +++ b/docs/requirements.md @@ -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 diff --git a/plans/gestor-coleccion-plan.md b/plans/gestor-coleccion-plan.md index f9af3d6..badb454 100644 --- a/plans/gestor-coleccion-plan.md +++ b/plans/gestor-coleccion-plan.md @@ -1,6 +1,6 @@ ## 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** @@ -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** -- **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:** - `docs/requirements.md` — requisitos funcionales y no funcionales - `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** -- **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:** - - `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/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/migrations/` — migraciones Prisma - `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 - **Pasos:** 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` 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** 🔧 - **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 - **APIs de metadata:** IGDB (primary), RAWG (fallback), TheGamesDB (artwork/retro) - **Tests:** Backend (Vitest + Supertest), Frontend (Vitest + React Testing Library), E2E (Playwright) diff --git a/tests/docs.spec.ts b/tests/docs.spec.ts index aff8b36..620ff33 100644 --- a/tests/docs.spec.ts +++ b/tests/docs.spec.ts @@ -24,7 +24,7 @@ test.describe('Docs content checks', () => { test('lessons-learned contains PoC and license compatibility note', async () => { const file = await fs.readFile(path.join(docsDir, 'lessons-learned.md'), 'utf-8'); 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('Fuentes');