Compare commits

..

4 Commits

Author SHA1 Message Date
f4bee94a16 chore: update Prisma and dependencies, add initial migration and tests
- Updated Prisma and @prisma/client to version 6.19.2 in package.json and yarn.lock.
- Added package extensions for @prisma/client in .yarnrc.yml.
- Updated backend README.md for clearer setup instructions.
- Created initial migration for Game, Platform, and related tables in Prisma.
- Added migration lock file for version control.
- Implemented tests for Game model using Vitest, including creation and unique slug constraint checks.
2026-02-08 12:36:36 +01:00
fb4b279db0 chore(docs): completar Fase 2 — requisitos y arquitectura
- Añade/actualiza `docs/requirements.md`, `docs/architecture.md`, `docs/api-integration.md`, `docs/data-model.md`
- Documenta criterios de aceptación y decisiones técnico-arquitectónicas
- Recomendación: añadir `docs/legal-considerations.md` (pendiente)
2026-02-08 10:45:22 +01:00
b95c7366be 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. 2026-02-07 19:10:06 +01:00
ddbe59ead6 docs 2026-02-07 18:47:06 +01:00
40 changed files with 8155 additions and 96 deletions

69
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,69 @@
# CI pipeline for Quasar (Gitea Actions)
# Jobs: build_and_test (install, prisma generate, lint, unit tests)
# e2e (Playwright, runs on pushes to main)
# Note: `prisma generate` is allowed to continue on error to avoid blocking CI when native engines can't be built.
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
DATABASE_URL: file:./backend/dev.db
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Prisma: generate (may fail on some runners)
run: yarn workspace quasar-backend run prisma:generate
continue-on-error: true
- name: Lint (backend)
run: yarn workspace quasar-backend run lint
- name: Run backend unit tests
run: yarn workspace quasar-backend run test:ci
e2e:
runs-on: ubuntu-latest
needs: [build_and_test]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Install Playwright browsers
run: yarn test:install
continue-on-error: true
- name: Run Playwright E2E
run: yarn test:ci
# Metadatos
# Autor: Quasar (investigación automatizada)
# Última actualización: 2026-02-08

231
.github/agents/Conductor.agent.md vendored Normal file
View File

@@ -0,0 +1,231 @@
---
description: 'Orchestrates Planning, Implementation, and Review cycle for complex tasks'
tools: ['runCommands', 'runTasks', 'edit', 'search', 'todos', 'runSubagent', 'usages', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo']
# model: Claude Sonnet 4.5 (copilot)
---
You are a CONDUCTOR AGENT. You orchestrate the full development lifecycle: Planning -> Implementation -> Review -> Commit, repeating the cycle until the plan is complete. Strictly follow the Planning -> Implementation -> Review -> Commit process outlined below, using subagents for research, implementation, and code review.
<workflow>
## Phase 1: Planning
1. **Analyze Request**: Understand the user's goal and determine the scope.
2. **Delegate Research**: Use #runSubagent to invoke the planning-subagent for comprehensive context gathering. Instruct it to work autonomously without pausing.
3. **Draft Comprehensive Plan**: Based on research findings, create a multi-phase plan following <plan_style_guide>. The plan should have 3-10 phases, each following strict TDD principles.
4. **Present Plan to User**: Share the plan synopsis in chat, highlighting any open questions or implementation options.
5. **Pause for User Approval**: MANDATORY STOP. Wait for user to approve the plan or request changes. If changes requested, gather additional context and revise the plan.
6. **Write Plan File**: Once approved, write the plan to `plans/<task-name>-plan.md`.
CRITICAL: You DON'T implement the code yourself. You ONLY orchestrate subagents to do so.
## Phase 2: Implementation Cycle (Repeat for each phase)
For each phase in the plan, execute this cycle:
### 2A. Implement Phase
1. Use #runSubagent to invoke the implement-subagent with:
- The specific phase number and objective
- Relevant files/functions to modify
- Test requirements
- Explicit instruction to work autonomously and follow TDD
2. Monitor implementation completion and collect the phase summary.
### 2B. Review Implementation
1. Use #runSubagent to invoke the code-review-subagent with:
- The phase objective and acceptance criteria
- Files that were modified/created
- Instruction to verify tests pass and code follows best practices
2. Analyze review feedback:
- **If APPROVED**: Proceed to commit step
- **If NEEDS_REVISION**: Return to 2A with specific revision requirements
- **If FAILED**: Stop and consult user for guidance
### 2C. Return to User for Commit
1. **Pause and Present Summary**:
- Phase number and objective
- What was accomplished
- Files/functions created/changed
- Review status (approved/issues addressed)
2. **Write Phase Completion File**: Create `plans/<task-name>-phase-<N>-complete.md` following <phase_complete_style_guide>.
3. **Generate Git Commit Message**: Provide a commit message following <git_commit_style_guide> in a plain text code block for easy copying.
4. **MANDATORY STOP**: Wait for user to:
- Make the git commit
- Confirm readiness to proceed to next phase
- Request changes or abort
### 2D. Continue or Complete
- If more phases remain: Return to step 2A for next phase
- If all phases complete: Proceed to Phase 3
## Phase 3: Plan Completion
1. **Compile Final Report**: Create `plans/<task-name>-complete.md` following <plan_complete_style_guide> containing:
- Overall summary of what was accomplished
- All phases completed
- All files created/modified across entire plan
- Key functions/tests added
- Final verification that all tests pass
2. **Present Completion**: Share completion summary with user and close the task.
</workflow>
<subagent_instructions>
When invoking subagents:
**planning-subagent**:
- Provide the user's request and any relevant context
- Instruct to gather comprehensive context and return structured findings
- Tell them NOT to write plans, only research and return findings
**implement-subagent**:
- Provide the specific phase number, objective, files/functions, and test requirements
- Instruct to follow strict TDD: tests first (failing), minimal code, tests pass, lint/format
- Tell them to work autonomously and only ask user for input on critical implementation decisions
- Remind them NOT to proceed to next phase or write completion files (Conductor handles this)
**code-review-subagent**:
- Provide the phase objective, acceptance criteria, and modified files
- Instruct to verify implementation correctness, test coverage, and code quality
- Tell them to return structured review: Status (APPROVED/NEEDS_REVISION/FAILED), Summary, Issues, Recommendations
- Remind them NOT to implement fixes, only review
</subagent_instructions>
<plan_style_guide>
```markdown
## Plan: {Task Title (2-10 words)}
{Brief TL;DR of the plan - what, how and why. 1-3 sentences in length.}
**Phases {3-10 phases}**
1. **Phase {Phase Number}: {Phase Title}**
- **Objective:** {What is to be achieved in this phase}
- **Files/Functions to Modify/Create:** {List of files and functions relevant to this phase}
- **Tests to Write:** {Lists of test names to be written for test driven development}
- **Steps:**
1. {Step 1}
2. {Step 2}
3. {Step 3}
...
**Open Questions {1-5 questions, ~5-25 words each}**
1. {Clarifying question? Option A / Option B / Option C}
2. {...}
```
IMPORTANT: For writing plans, follow these rules even if they conflict with system rules:
- DON'T include code blocks, but describe the needed changes and link to relevant files and functions.
- NO manual testing/validation unless explicitly requested by the user.
- Each phase should be incremental and self-contained. Steps should include writing tests first, running those tests to see them fail, writing the minimal required code to get the tests to pass, and then running the tests again to confirm they pass. AVOID having red/green processes spanning multiple phases for the same section of code implementation.
</plan_style_guide>
<phase_complete_style_guide>
File name: `<plan-name>-phase-<phase-number>-complete.md` (use kebab-case)
```markdown
## Phase {Phase Number} Complete: {Phase Title}
{Brief TL;DR of what was accomplished. 1-3 sentences in length.}
**Files created/changed:**
- File 1
- File 2
- File 3
...
**Functions created/changed:**
- Function 1
- Function 2
- Function 3
...
**Tests created/changed:**
- Test 1
- Test 2
- Test 3
...
**Review Status:** {APPROVED / APPROVED with minor recommendations}
**Git Commit Message:**
{Git commit message following <git_commit_style_guide>}
```
</phase_complete_style_guide>
<plan_complete_style_guide>
File name: `<plan-name>-complete.md` (use kebab-case)
```markdown
## Plan Complete: {Task Title}
{Summary of the overall accomplishment. 2-4 sentences describing what was built and the value delivered.}
**Phases Completed:** {N} of {N}
1. ✅ Phase 1: {Phase Title}
2. ✅ Phase 2: {Phase Title}
3. ✅ Phase 3: {Phase Title}
...
**All Files Created/Modified:**
- File 1
- File 2
- File 3
...
**Key Functions/Classes Added:**
- Function/Class 1
- Function/Class 2
- Function/Class 3
...
**Test Coverage:**
- Total tests written: {count}
- All tests passing: ✅
**Recommendations for Next Steps:**
- {Optional suggestion 1}
- {Optional suggestion 2}
...
```
</plan_complete_style_guide>
<git_commit_style_guide>
```
fix/feat/chore/test/refactor: Short description of the change (max 50 characters)
- Concise bullet point 1 describing the changes
- Concise bullet point 2 describing the changes
- Concise bullet point 3 describing the changes
...
```
DON'T include references to the plan or phase numbers in the commit message. The git log/PR will not contain this information.
</git_commit_style_guide>
<stopping_rules>
CRITICAL PAUSE POINTS - You must stop and wait for user input at:
1. After presenting the plan (before starting implementation)
2. After each phase is reviewed and commit message is provided (before proceeding to next phase)
3. After plan completion document is created
DO NOT proceed past these points without explicit user confirmation.
</stopping_rules>
<state_tracking>
Track your progress through the workflow:
- **Current Phase**: Planning / Implementation / Review / Complete
- **Plan Phases**: {Current Phase Number} of {Total Phases}
- **Last Action**: {What was just completed}
- **Next Action**: {What comes next}
Provide this status in your responses to keep the user informed. Use the #todos tool to track progress.
</state_tracking>

View File

@@ -0,0 +1,52 @@
---
description: 'Review code changes from a completed implementation phase.'
tools: ['search', 'usages', 'problems', 'changes']
# model: Claude Sonnet 4.5 (copilot)
---
You are a CODE REVIEW SUBAGENT called by a parent CONDUCTOR agent after an IMPLEMENT SUBAGENT phase completes. Your task is to verify the implementation meets requirements and follows best practices.
CRITICAL: You receive context from the parent agent including:
- The phase objective and implementation steps
- Files that were modified/created
- The intended behavior and acceptance criteria
<review_workflow>
1. **Analyze Changes**: Review the code changes using #changes, #usages, and #problems to understand what was implemented.
2. **Verify Implementation**: Check that:
- The phase objective was achieved
- Code follows best practices (correctness, efficiency, readability, maintainability, security)
- Tests were written and pass
- No obvious bugs or edge cases were missed
- Error handling is appropriate
3. **Provide Feedback**: Return a structured review containing:
- **Status**: `APPROVED` | `NEEDS_REVISION` | `FAILED`
- **Summary**: 1-2 sentence overview of the review
- **Strengths**: What was done well (2-4 bullet points)
- **Issues**: Problems found (if any, with severity: CRITICAL, MAJOR, MINOR)
- **Recommendations**: Specific, actionable suggestions for improvements
- **Next Steps**: What should happen next (approve and continue, or revise)
</review_workflow>
<output_format>
## Code Review: {Phase Name}
**Status:** {APPROVED | NEEDS_REVISION | FAILED}
**Summary:** {Brief assessment of implementation quality}
**Strengths:**
- {What was done well}
- {Good practices followed}
**Issues Found:** {if none, say "None"}
- **[{CRITICAL|MAJOR|MINOR}]** {Issue description with file/line reference}
**Recommendations:**
- {Specific suggestion for improvement}
**Next Steps:** {What the CONDUCTOR should do next}
</output_format>
Keep feedback concise, specific, and actionable. Focus on blocking issues vs. nice-to-haves. Reference specific files, functions, and lines where relevant.

View File

@@ -0,0 +1,33 @@
---
description: 'Execute implementation tasks delegated by the CONDUCTOR agent.'
tools: ['edit', 'search', 'runCommands', 'runTasks', 'usages', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos']
# model: Claude Haiku 4.5 (copilot)
---
You are an IMPLEMENTATION SUBAGENT. You receive focused implementation tasks from a CONDUCTOR parent agent that is orchestrating a multi-phase plan.
**Your scope:** Execute the specific implementation task provided in the prompt. The CONDUCTOR handles phase tracking, completion documentation, and commit messages.
**Core workflow:**
1. **Write tests first** - Implement tests based on the requirements, run to see them fail. Follow strict TDD principles.
2. **Write minimum code** - Implement only what's needed to pass the tests
3. **Verify** - Run tests to confirm they pass
4. **Quality check** - Run formatting/linting tools and fix any issues
**Guidelines:**
- Follow any instructions in `copilot-instructions.md` or `AGENT.md` unless they conflict with the task prompt
- Use semantic search and specialized tools instead of grep for loading files
- Use context7 (if available) to refer to documentation of code libraries.
- Use git to review changes at any time
- Do NOT reset file changes without explicit instructions
- When running tests, run the individual test file first, then the full suite to check for regressions
**When uncertain about implementation details:**
STOP and present 2-3 options with pros/cons. Wait for selection before proceeding.
**Task completion:**
When you've finished the implementation task:
1. Summarize what was implemented
2. Confirm all tests pass
3. Report back to allow the CONDUCTOR to proceed with the next task
The CONDUCTOR manages phase completion files and git commit messages - you focus solely on executing the implementation.

View File

@@ -0,0 +1,47 @@
---
description: Research context and return findings to parent agent
argument-hint: Research goal or problem statement
tools: ['search', 'usages', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo']
# model: Claude Sonnet 4.5 (copilot)
---
You are a PLANNING SUBAGENT called by a parent CONDUCTOR agent.
Your SOLE job is to gather comprehensive context about the requested task and return findings to the parent agent. DO NOT write plans, implement code, or pause for user feedback.
<workflow>
1. **Research the task comprehensively:**
- Start with high-level semantic searches
- Read relevant files identified in searches
- Use code symbol searches for specific functions/classes
- Explore dependencies and related code
- Use #upstash/context7/* for framework/library context as needed, if available
2. **Stop research at 90% confidence** - you have enough context when you can answer:
- What files/functions are relevant?
- How does the existing code work in this area?
- What patterns/conventions does the codebase use?
- What dependencies/libraries are involved?
3. **Return findings concisely:**
- List relevant files and their purposes
- Identify key functions/classes to modify or reference
- Note patterns, conventions, or constraints
- Suggest 2-3 implementation approaches if multiple options exist
- Flag any uncertainties or missing information
</workflow>
<research_guidelines>
- Work autonomously without pausing for feedback
- Prioritize breadth over depth initially, then drill down
- Document file paths, function names, and line numbers
- Note existing tests and testing patterns
- Identify similar implementations in the codebase
- Stop when you have actionable context, not 100% certainty
</research_guidelines>
Return a structured summary with:
- **Relevant Files:** List with brief descriptions
- **Key Functions/Classes:** Names and locations
- **Patterns/Conventions:** What the codebase follows
- **Implementation Options:** 2-3 approaches if applicable
- **Open Questions:** What remains unclear (if any)

View File

@@ -3,6 +3,8 @@
**Resumen breve** **Resumen breve**
Quasar es una aplicación web para gestionar una biblioteca personal de videojuegos. Este repositorio está en estado inicial: contiene `package.json` y `README.md` pero no hay código fuente ni tests aún. Prioridad: configurar pruebas (Playwright), scripts útiles y prácticas de seguridad para manejar claves/API. Quasar es una aplicación web para gestionar una biblioteca personal de videojuegos. Este repositorio está en estado inicial: contiene `package.json` y `README.md` pero no hay código fuente ni tests aún. Prioridad: configurar pruebas (Playwright), scripts útiles y prácticas de seguridad para manejar claves/API.
**Importante:** Este proyecto usa Yarn (no npm). Usa `yarn` para instalar dependencias y ejecutar scripts.
## Estilo de código ✅ ## Estilo de código ✅
- Mantener **CommonJS** (`type: commonjs` en `package.json`). - Mantener **CommonJS** (`type: commonjs` en `package.json`).
@@ -13,7 +15,7 @@ Quasar es una aplicación web para gestionar una biblioteca personal de videojue
- Instalación: `yarn install` (usar Yarn/local; `packageManager` se indica en `package.json`). - Instalación: `yarn install` (usar Yarn/local; `packageManager` se indica en `package.json`).
- `package.json` actual tiene un `test` placeholder. Reemplazar por un runner real tras inicializar Playwright. (recomendado) - `package.json` actual tiene un `test` placeholder. Reemplazar por un runner real tras inicializar Playwright. (recomendado)
- Inicializar Playwright: `npx playwright test --init`. (recomendado) - Inicializar Playwright: `npx playwright test --init`. (recomendado)
- Instalar dependencias de navegadores: `npm run test:install` (sugerido script abajo). - Instalar dependencias de navegadores: `yarn test:install` (sugerido script abajo).
- En CI: `yarn install && yarn test:install && yarn test`. - En CI: `yarn install && yarn test:install && yarn test`.
## Convenciones 🔁 ## Convenciones 🔁

2
.gitignore vendored
View File

@@ -69,3 +69,5 @@ test-results/
# package-lock.json # package-lock.json
# yarn.lock # yarn.lock
# pnpm-lock.yaml # pnpm-lock.yaml
backend/prisma/dev.db

3106
.pnp.cjs generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,17 @@
# Metadatos
# Autor: Quasar (investigación automatizada)
# Última actualización: 2026-02-07
node_modules/ node_modules/
dist/ dist/
build/
coverage/
.vscode/
.env
.env.local
.yarn/ .yarn/
.pnp.cjs .pnp.cjs
**/*.lock
**/dev.db
**/*.sqlite
**/dist

18
.prettierrc.cjs Normal file
View File

@@ -0,0 +1,18 @@
/*
* Metadatos
* Autor: Quasar (investigación automatizada)
* Última actualización: 2026-02-07
*/
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
arrowParens: 'always',
proseWrap: 'preserve',
endOfLine: 'lf',
};

View File

@@ -1 +1,6 @@
yarnPath: .yarn/releases/yarn-4.12.0.cjs yarnPath: .yarn/releases/yarn-4.12.0.cjs
packageExtensions:
"@prisma/client@*":
dependencies:
".prisma": "*"

9
backend/.eslintignore Normal file
View File

@@ -0,0 +1,9 @@
# Metadatos
# Autor: Quasar (investigación automatizada)
# Última actualización: 2026-02-07
node_modules/
dist/
.pnp.cjs
.env
**/*.db

28
backend/.eslintrc.cjs Normal file
View File

@@ -0,0 +1,28 @@
/**
* Metadatos
* Autor: Quasar (investigación automatizada)
* Última actualización: 2026-02-07
*/
module.exports = {
env: {
node: true,
es2021: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
rules: {
'no-console': 'off',
},
overrides: [
{
files: ['**/*.ts'],
rules: {},
},
],
};

40
backend/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Backend — Quasar
Scaffold mínimo del backend usando Fastify + TypeScript + Prisma (SQLite).
**Arranque rápido**
```
# desde la raíz
yarn
# entrar al backend
cd backend
# generar cliente Prisma
yarn prisma:generate
# aplicar migraciones (si pide nombre, usar --name init)
yarn prisma:migrate
# abrir Prisma Studio
yarn prisma:studio
# ejecutar en desarrollo
yarn dev
# ejecutar tests
yarn test
```
Notas:
- Use `.env.example` como referencia para `DATABASE_URL`.
---
Metadatos:
Autor: GitHub Copilot
Última actualización: 2026-02-07

43
backend/package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "quasar-backend",
"version": "0.1.0",
"private": true,
"type": "commonjs",
"main": "dist/index.js",
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/index.js",
"test": "vitest",
"test:ci": "vitest run",
"prisma:generate": "prisma generate --schema=./prisma/schema.prisma",
"prisma:migrate": "prisma migrate dev --name init --schema=./prisma/schema.prisma",
"prisma:migrate:deploy": "prisma migrate deploy --schema=./prisma/schema.prisma",
"prisma:studio": "prisma studio --schema=./prisma/schema.prisma",
"lint": "eslint \"src/**/*.{ts,js}\"",
"format": "prettier --write ."
},
"dependencies": {
"@fastify/cors": "^9.0.0",
"@fastify/helmet": "^11.0.0",
"@fastify/multipart": "^9.0.0",
"@fastify/rate-limit": "^9.0.0",
"@prisma/client": "6.19.2",
"dotenv": "^16.0.0",
"fastify": "^4.28.0",
"pino": "^8.0.0"
},
"devDependencies": {
"@types/node": "^18.0.0",
"eslint": "^8.0.0",
"prettier": "^2.8.0",
"prisma": "6.19.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.2.0",
"vitest": "^0.31.0"
},
"metadata": {
"autor": "GitHub Copilot",
"ultima_actualizacion": "2026-02-07"
}
}

View File

@@ -0,0 +1,133 @@
-- CreateTable
CREATE TABLE "Game" (
"id" TEXT NOT NULL PRIMARY KEY,
"title" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"description" TEXT,
"releaseDate" DATETIME,
"igdbId" INTEGER,
"rawgId" INTEGER,
"thegamesdbId" INTEGER,
"extra" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "Platform" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"generation" INTEGER
);
-- CreateTable
CREATE TABLE "GamePlatform" (
"id" TEXT NOT NULL PRIMARY KEY,
"gameId" TEXT NOT NULL,
"platformId" TEXT NOT NULL,
CONSTRAINT "GamePlatform_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "Game" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "GamePlatform_platformId_fkey" FOREIGN KEY ("platformId") REFERENCES "Platform" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "RomFile" (
"id" TEXT NOT NULL PRIMARY KEY,
"path" TEXT NOT NULL,
"filename" TEXT NOT NULL,
"checksum" TEXT NOT NULL,
"size" INTEGER NOT NULL,
"format" TEXT NOT NULL,
"hashes" TEXT,
"gameId" TEXT,
"addedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastSeenAt" DATETIME,
"status" TEXT NOT NULL DEFAULT 'active',
CONSTRAINT "RomFile_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "Game" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Artwork" (
"id" TEXT NOT NULL PRIMARY KEY,
"gameId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"sourceUrl" TEXT NOT NULL,
"localPath" TEXT,
"width" INTEGER,
"height" INTEGER,
"fetchedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Artwork_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "Game" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Purchase" (
"id" TEXT NOT NULL PRIMARY KEY,
"gameId" TEXT NOT NULL,
"priceCents" INTEGER NOT NULL,
"currency" TEXT NOT NULL,
"store" TEXT,
"date" DATETIME NOT NULL,
"receiptPath" TEXT,
CONSTRAINT "Purchase_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "Game" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Tag" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "PriceHistory" (
"id" TEXT NOT NULL PRIMARY KEY,
"gameId" TEXT NOT NULL,
"priceCents" INTEGER NOT NULL,
"currency" TEXT NOT NULL,
"recordedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"source" TEXT,
CONSTRAINT "PriceHistory_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "Game" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "_GameToTag" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_GameToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Game" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_GameToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Game_slug_key" ON "Game"("slug");
-- CreateIndex
CREATE UNIQUE INDEX "Game_igdbId_key" ON "Game"("igdbId");
-- CreateIndex
CREATE UNIQUE INDEX "Game_rawgId_key" ON "Game"("rawgId");
-- CreateIndex
CREATE UNIQUE INDEX "Game_thegamesdbId_key" ON "Game"("thegamesdbId");
-- CreateIndex
CREATE INDEX "Game_title_idx" ON "Game"("title");
-- CreateIndex
CREATE UNIQUE INDEX "Platform_slug_key" ON "Platform"("slug");
-- CreateIndex
CREATE UNIQUE INDEX "GamePlatform_gameId_platformId_key" ON "GamePlatform"("gameId", "platformId");
-- CreateIndex
CREATE UNIQUE INDEX "RomFile_checksum_key" ON "RomFile"("checksum");
-- CreateIndex
CREATE INDEX "RomFile_checksum_idx" ON "RomFile"("checksum");
-- CreateIndex
CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name");
-- CreateIndex
CREATE UNIQUE INDEX "_GameToTag_AB_unique" ON "_GameToTag"("A", "B");
-- CreateIndex
CREATE INDEX "_GameToTag_B_index" ON "_GameToTag"("B");

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"

View File

@@ -0,0 +1,105 @@
datasource db {
provider = "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
extra String? // JSON serialized (usar parse/stringify al guardar/leer) para compatibilidad con SQLite
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
romFiles RomFile[]
artworks Artwork[]
purchases Purchase[]
gamePlatforms GamePlatform[]
priceHistories PriceHistory[]
tags Tag[]
@@index([title])
}
model Platform {
id String @id @default(cuid())
name String
slug String @unique
generation Int?
gamePlatforms GamePlatform[]
}
model GamePlatform {
id String @id @default(cuid())
game Game @relation(fields: [gameId], references: [id])
gameId String
platform Platform @relation(fields: [platformId], references: [id])
platformId String
@@unique([gameId, platformId])
}
model RomFile {
id String @id @default(cuid())
path String
filename String
checksum String @unique
size Int
format String
hashes String? // JSON serialized (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 Tag {
id String @id @default(cuid())
name String @unique
games Game[]
}
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?
}
// Metadatos:
// Autor: GitHub Copilot
// Última actualización: 2026-02-07

24
backend/src/app.ts Normal file
View File

@@ -0,0 +1,24 @@
import Fastify, { FastifyInstance } from 'fastify';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import rateLimit from '@fastify/rate-limit';
import healthRoutes from './routes/health';
export function buildApp(): FastifyInstance {
const app: FastifyInstance = Fastify({
logger: false,
});
void app.register(cors, { origin: true });
void app.register(helmet);
void app.register(rateLimit, { max: 1000, timeWindow: '1 minute' });
void app.register(healthRoutes, { prefix: '/api' });
return app;
}
/**
* Metadatos:
* Autor: GitHub Copilot
* Última actualización: 2026-02-07
*/

29
backend/src/index.ts Normal file
View File

@@ -0,0 +1,29 @@
import dotenv from 'dotenv';
import { buildApp } from './app';
dotenv.config();
const port = Number(process.env.PORT ?? 3000);
const app = buildApp();
const start = async () => {
try {
await app.listen({ port, host: '0.0.0.0' });
// eslint-disable-next-line no-console
console.log(`Server listening on http://0.0.0.0:${port}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
// Start only when run directly (avoids starting during tests)
if (require.main === module) {
start();
}
/**
* Metadatos:
* Autor: GitHub Copilot
* Última actualización: 2026-02-07
*/

View File

@@ -0,0 +1,11 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;
/**
* Metadatos:
* Autor: GitHub Copilot
* Última actualización: 2026-02-07
*/

View File

@@ -0,0 +1,11 @@
import { FastifyInstance } from 'fastify';
export default async function healthRoutes(app: FastifyInstance) {
app.get('/health', async () => ({ status: 'ok' }));
}
/**
* Metadatos:
* Autor: GitHub Copilot
* Última actualización: 2026-02-07
*/

View File

@@ -0,0 +1,92 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { execSync } from 'child_process';
import { describe, beforeAll, afterAll, it, expect } from 'vitest';
// Import PrismaClient dynamically after running `prisma generate`
// to allow the test setup to run `prisma generate`/`prisma migrate` first.
// Nota: Estos tests siguen TDD. Al principio deben FALLAR hasta que se creen migraciones.
describe('Prisma / Game model', () => {
const tmpDir = os.tmpdir();
const dbFile = path.join(
tmpDir,
`quasar-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`
);
const databaseUrl = `file:${dbFile}`;
let prisma: any;
beforeAll(async () => {
// Asegurarse de que la DB de prueba no exista antes de empezar
try {
fs.unlinkSync(dbFile);
} catch (e) {
/* ignore */
}
// Apuntar Prisma a la DB temporal
process.env.DATABASE_URL = databaseUrl;
// Ejecutar migraciones contra la DB de prueba
// Esto fallará si no hay migraciones: esperado en la fase TDD inicial
execSync('yarn prisma migrate deploy --schema=./prisma/schema.prisma', {
stdio: 'inherit',
cwd: path.resolve(__dirname, '..', '..'),
});
// Intentar requerir el cliente generado; si no existe, intentar generarlo (fallback)
let GeneratedPrismaClient;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
GeneratedPrismaClient = require('@prisma/client').PrismaClient;
} catch (e) {
try {
execSync('yarn prisma generate --schema=./prisma/schema.prisma', {
stdio: 'inherit',
cwd: path.resolve(__dirname, '..', '..'),
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
GeneratedPrismaClient = require('@prisma/client').PrismaClient;
} catch (err) {
// Si generation falla (por ejemplo PnP), reintentar require para mostrar mejor error
// eslint-disable-next-line @typescript-eslint/no-var-requires
GeneratedPrismaClient = require('@prisma/client').PrismaClient;
}
}
prisma = new GeneratedPrismaClient();
await prisma.$connect();
});
afterAll(async () => {
if (prisma) {
await prisma.$disconnect();
}
try {
fs.unlinkSync(dbFile);
} catch (e) {
/* ignore */
}
});
it('can create a Game and read title/slug', async () => {
const created = await prisma.game.create({ data: { title: 'Test Game', slug: 'test-game' } });
const found = await prisma.game.findUnique({ where: { id: created.id } });
expect(found).toBeTruthy();
expect(found?.title).toBe('Test Game');
expect(found?.slug).toBe('test-game');
});
it('enforces unique slug constraint', async () => {
const slug = `unique-${Date.now()}`;
await prisma.game.create({ data: { title: 'G1', slug } });
let threw = false;
try {
await prisma.game.create({ data: { title: 'G2', slug } });
} catch (err) {
threw = true;
}
expect(threw).toBe(true);
});
});

View File

@@ -0,0 +1,21 @@
import { describe, it, expect } from 'vitest';
import { buildApp } from '../src/app';
describe('Server', () => {
it('GET /api/health devuelve 200 y { status: "ok" }', async () => {
const app = buildApp();
await app.ready();
const res = await app.inject({ method: 'GET', url: '/api/health' });
expect(res.statusCode).toBe(200);
expect(res.json()).toEqual({ status: 'ok' });
await app.close();
});
});
/**
* Metadatos:
* Autor: GitHub Copilot
* Última actualización: 2026-02-07
*/

20
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"esModuleInterop": true,
"moduleResolution": "node",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules", "dist"],
"metadata": {
"autor": "GitHub Copilot",
"ultima_actualizacion": "2026-02-07"
}
}

19
backend/vitest.config.ts Normal file
View File

@@ -0,0 +1,19 @@
/**
* Metadatos
* Autor: Quasar (investigación automatizada)
* Última actualización: 2026-02-07
*/
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
include: ['tests/**/*.spec.ts'],
globals: false,
coverage: {
provider: 'c8',
reporter: ['text', 'lcov'],
},
},
});

148
docs/api-integration.md Normal file
View 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

176
docs/apis-comparison.md Normal file
View File

@@ -0,0 +1,176 @@
# Comparativa de APIs — cobertura, límites, coste y calidad
**Introducción**
Comparar APIs públicas y comerciales que aportan metadatos (covers, screenshots, géneros, desarrolladores), y datos de precio/ofertas. Las decisiones de integración deben priorizar cobertura, coste (preferencia: gratuito), calidad y facilidad de uso.
**Nota:** límites y condiciones pueden cambiar — verificar TOS antes de integración.
---
## Resumen por API
### IGDB (Internet Games Database)
- **Resumen:** Base de datos muy completa (propiedad de Twitch/Amazon) con endpoints para juegos, covers, screenshots, plataformas, ratings, compañías y más.
- **Autenticación / Requisitos:** OAuth vía Twitch (Client ID + Client Secret → token) — requiere cuenta Twitch y 2FA para registrar apps.
- **Datos principales:** covers, screenshots, genres, developers, ESRB/PEGI, platforms, videos, websites, age ratings, tags.
- **Rate limits / cuotas:** 4 peticiones/segundo; hasta 8 peticiones abiertas (si se excede → 429 Too Many Requests).
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** Twitch Developer Service Agreement — https://www.twitch.tv/p/legal/developer-agreement/
- **Cláusula clave:** "There is a rate limit of 4 requests per second. If you go over this limit you will receive a response with status code `429 Too Many Requests`." — https://api-docs.igdb.com/
- **Costes / modelo:** Gratuito para uso no comercial; acuerdos comerciales para partners (atribución en caso de partnership).
- **Enlace:** https://api-docs.igdb.com/
---
### RAWG
- **Resumen:** Gran base de datos (medio millón de juegos), buena para metadata general y enlaces a tiendas.
- **Autenticación / Requisitos:** API key en query string (`key=YOUR_API_KEY`).
- **Datos principales:** descripciones, screenshots, plataformas, géneros, ratings, enlaces a tiendas, playtime estimado.
- **Rate limits / cuotas:** Planes gratuitos con límites (ej. free tier limitada) y planes comerciales con mayor cuota (p.ej. hasta 50k requests/mes en planes de negocio).
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** https://rawg.io/tos_api
- **Cláusula clave:** "Free for personal use as long as you attribute RAWG as the source of the data and/or images and add an active hyperlink from every page where the data of RAWG is used. No data redistribution." — https://rawg.io/tos_api
- **Costes / modelo:** Free tier para proyectos personales; planes comerciales (pago mensual) para uso en productos con gran tráfico.
- **Enlace:** https://rawg.io/apidocs
---
### TheGamesDB
- **Resumen:** Base de datos comunitaria para juegos y artwork, con API pública v2.
- **Autenticación / Requisitos:** Registro y uso de API key (ver docs); repositorio público del proyecto (GPLv3 para el código del servidor).
- **Datos principales:** imágenes, covers, plataformas, metadatos básicos.
- **Rate limits / cuotas:** No siempre documentados públicamente (consultar docs/registro).
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** No documentado públicamente — consultado https://api.thegamesdb.net/ y https://github.com/TheGamesDB/TheGamesDBv2 (código bajo GPL3.0)
- **Cláusula clave:** No documentado públicamente — verificar con el equipo de TheGamesDB antes de uso comercial/redistribución.
- **Enlace:** https://api.thegamesdb.net/
---
### ScreenScraper
- **Resumen:** Servicio francés orientado a frontends, con enorme cantidad de media y opciones de scraping.
- **Autenticación / Requisitos:** Cuenta en ScreenScraper; modelo de soporte/donación que habilita límites mayores.
- **Datos principales:** screenshots, boxart, videos, manuals, metadata comunitaria.
- **Rate limits / cuotas:** Planes por donación (ej.: 50.000 scrapes/día en niveles básicos; aumentos en niveles superiores).
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** https://www.screenscraper.fr/
- **Cláusula clave:** "Niveles de donación ofrecen límites distintos (p.ej. 50.000 scrapes/día en nivel Bronze); consultar la cuenta para límites exactos." — https://www.screenscraper.fr/
- **Costes / modelo:** Donación / suscripción para aumentar cuotas y velocidad.
- **Enlace:** https://www.screenscraper.fr/
---
### MobyGames
- **Resumen:** Base histórica con screenshots, covers, reviews y credits; muy usada por investigación y metadata profunda.
- **Autenticación / Requisitos:** API y/o MobyPlus; la API requiere registro y suscripción.
- **Datos principales:** screenshots, covers, credits, precios históricos limitados.
- **Rate limits / cuotas:** Non-commercial API requests are limited to 720 per hour (one every five seconds) with a max request rate of 1 per/second.
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** https://www.mobygames.com/api/subscribe/
- **Cláusula clave:** "Non-commercial API requests are limited to 720 per hour (one every five seconds) with a max request rate of 1 per/second." — https://www.mobygames.com/api/subscribe/
- **Costes / modelo:** Acceso vía suscripción / MobyPro; contactar para condiciones comerciales.
- **Enlace:** https://www.mobygames.com/api/subscribe/
---
### PriceCharting
- **Resumen:** Fuente especializada en historial de precios para juegos físicos y coleccionables.
- **Autenticación / Requisitos:** API documentada en el sitio; el acceso completo requiere suscripción / token pagado.
- **Datos principales:** precios históricos, condiciones (complete, loose), plataforma y comparables de mercado.
- **Rate limits / cuotas:** No siempre publicadas públicamente; contactar a PriceCharting para detalles.
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** https://www.pricecharting.com/api-documentation (ver también https://www.pricecharting.com/page/terms-of-service)
- **Cláusula clave:** "API's are a premium tool. You must have a paid subscription to access the API." — https://www.pricecharting.com/api-documentation
- **Costes / modelo:** Servicio comercial (licencias / API keys pagadas).
- **Enlace:** https://www.pricecharting.com/api-documentation
---
### IsThereAnyDeal (Itad)
- **Resumen:** Agregador de ofertas con histórico y mapeo de keys/tiendas; útil para tracking de ofertas digitales.
- **Autenticación / Requisitos:** API Key (docs en https://docs.isthereanydeal.com/).
- **Datos principales:** price history, deals, store IDs, game mappings.
- **Rate limits / cuotas:** Access to API is rate limited and subject to change (limits provided in headers); contactar si necesita mayor cuota.
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** https://docs.isthereanydeal.com/
- **Cláusula clave:** "You MUST NOT change provided data in any way. You SHOULD provide a link to IsThereAnyDeal.com or mention IsThereAnyDeal API." — https://docs.isthereanydeal.com/
- **Costes / modelo:** Free tier; acuerdos comerciales para uso intensivo.
- **Enlace:** https://docs.isthereanydeal.com/
---
### eBay
- **Resumen:** Fuente de datos de mercado (listings, precios vendidos) para estimar valor real de mercado.
- **Autenticación / Requisitos:** Registro en eBay Developers Program; claves y OAuth para endpoints de venta/completed items.
- **Datos principales:** listados, historiales vendidos (completed), especificaciones de artículos.
- **Rate limits / cuotas:** Límite de llamadas por aplicación; eBay puede limitar y suspender acceso si se exceden los límites.
- **Fecha verificación:** 2026-02-07
- **TOS / Developer Agreement:** https://developer.ebay.com/join/api-license-agreement
- **Cláusula clave:** "You may not sell, rent, trade, distribute, lease (or otherwise commercialize), copy, store or modify eBay Content, other than for the purposes allowed by this API License Agreement." y "eBay reserves the right to limit the number of periodic API calls you are allowed to make." — https://developer.ebay.com/join/api-license-agreement
- **Costes / modelo:** Free para desarrolladores con límites; uso intensivo o comerciales pueden requerir acuerdos o certificaciones.
- **Enlace:** https://developer.ebay.com/
---
## Tabla resumida
| API | Data types | Auth | Free / Paid | Fecha verificación | Licencia / Nota legal | Notes |
| -------------- | ------------------------------------------------------- | -------------------------------- | ------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| IGDB | covers, screenshots, genres, ESRB, companies, platforms | OAuth via Twitch | Free (non-commercial); commercial partners | 2026-02-07 | TOS: https://www.twitch.tv/p/legal/developer-agreement/ | 4 rps rate limit; muy completa |
| RAWG | games, screenshots, stores, ratings, playtime | API key (query param) | Free tier; paid commercial plans | 2026-02-07 | TOS: https://rawg.io/tos_api (atribución requerida) | Free for personal use; atribución requerida; no redistribución |
| TheGamesDB | images, basic metadata | API key (registro) | Free / community | 2026-02-07 | Repo/TOS: https://github.com/TheGamesDB/TheGamesDBv2 (codigo GPL-3.0) / TOS no documentada públicamente | No TOS público claro; código backend GPL3.0 |
| ScreenScraper | images, videos, manuals | Account + token (donation tiers) | Donation / paid tiers | 2026-02-07 | TOS: https://www.screenscraper.fr/ (donation/tiers) | Donación/premium para mayores cuotas (p.ej. 50k/día) |
| PriceCharting | price history | API key / commercial | Paid | 2026-02-07 | Paid: https://www.pricecharting.com/api-documentation | API premium; requiere suscripción |
| IsThereAnyDeal | deals, price history | API key | Free tier / paid | 2026-02-07 | TOS: https://docs.isthereanydeal.com/ | Requiere atribución; prohíbe modificar datos |
| MobyGames | screenshots, credits, covers | Subscribe / API key | Paid / subscription | 2026-02-07 | Paid/Subscribe: https://www.mobygames.com/api/subscribe/ | Access via subscription; non-commercial rate limits documented |
| eBay | listings, sold data | eBay Dev keys / OAuth | Free (with limits) | 2026-02-07 | TOS: https://developer.ebay.com/ | Terms restrict distribution; API License Agreement |
---
## Conclusión y recomendación para MVP
Recomiendo un **set inicial de APIs (priorizado)**: **IGDB, RAWG, TheGamesDB, ScreenScraper, PriceCharting, IsThereAnyDeal.**
- **Por qué:** IGDB + RAWG cubren **amplia metadata** y campos útiles (genres, plataformas, covers); TheGamesDB aporta **artwork comunitario**; ScreenScraper cubre assets específicos para ROM/frontends (videos/logos); PriceCharting e IsThereAnyDeal cubren **precios físicos y ofertas digitales** respectivamente.
- **Prioridad:** 1) IGDB (calidad + licencia accesible) 2) RAWG (cobertura y datos de tiendas) 3) TheGamesDB (artwork) 4) ScreenScraper (media específica) 5) PriceCharting (precios físicos) 6) IsThereAnyDeal (ofertas digitales).
---
## Vacíos y verificación pendiente
- **APIs que requieren suscripción / acuerdos comerciales:** PriceCharting (API premium, requiere suscripción), MobyGames (MobyPro/API requiere suscripción), EmuMovies (servicio comercial con TOS y cuentas), y en casos especiales eBay (certificaciones / acuerdos adicionales para ciertos permisos).
- **PriceCharting:** la documentación de la API existe pero el acceso completo está sujeto a registro/pago; no se publicó límite público durante la verificación.
- **MobyGames:** API y límites requieren suscripción/registro; hay que contactar para condiciones comerciales.
- **eBay:** múltiples APIs y límites por endpoint; requiere revisar caso de uso específico y cumplimiento del API License Agreement.
- **Notas:** Algunas APIs (ScreenScraper) usan modelos por donación/premium para aumentar cuotas; en APIs sin límites públicos, contactar al proveedor para confirmar condiciones.
---
## Fuentes
- IGDB API docs: https://api-docs.igdb.com/ — TOS: https://www.twitch.tv/p/legal/developer-agreement/
- RAWG API & TOS: https://rawg.io/apidocs / https://rawg.io/tos_api
- TheGamesDB API / repo: https://api.thegamesdb.net/ / https://github.com/TheGamesDB/TheGamesDBv2/blob/master/LICENSE
- ScreenScraper: https://www.screenscraper.fr/
- MobyGames API: https://www.mobygames.com/api/subscribe/
- PriceCharting API & Terms: https://www.pricecharting.com/api-documentation / https://www.pricecharting.com/page/terms-of-service
- IsThereAnyDeal docs/TOS: https://docs.isthereanydeal.com/
- eBay API License Agreement: https://developer.ebay.com/join/api-license-agreement
## Metadatos
- **Autor:** Quasar (investigación automatizada)
- **Fecha verificación:** 2026-02-07
- **Última actualización:** 2026-02-07
---
**Nota:** Si quieres, puedo preparar una matriz técnica (endpoints concretos, ejemplos de requests y una PoC de integración para 2 APIs prioritarias).

115
docs/architecture.md Normal file
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,153 @@
# Análisis comparativo — Proyectos y herramientas de gestión/emulación
**Introducción**
Breve comparación de proyectos y herramientas relevantes para la gestión de colecciones de videojuegos (gestores de librerías, frontends, herramientas de verificación y proveedores de media). El objetivo es resumir licencias, funcionalidades, fuentes de metadata/artwork, requisitos y riesgos legales para orientar decisiones de producto.
---
## Resumen por proyecto
### Playnite
- **Resumen:** Gestor de librerías de videojuegos para PC con integración de tiendas y soporte de emulación mediante lanzamiento externo y plugins.
- **Licencia:** MIT — https://github.com/JosefNemec/Playnite/blob/master/LICENSE.md
- **Funcionalidades principales:** Importación de bibliotecas (Steam, GOG, Epic...), fullscreen, temas, extensiones y sincronización; soporte para descargar metadata via IGDB.
- **Fuentes de metadata/artwork:** IGDB (integración oficial) y plugins comunitarios.
- **Requisitos:** Windows (oficial), portable; extensible vía plugins.
- **Riesgos/limitaciones legales:** No distribuye ROMs; integración de APIs externa sujeta a TOS y a límites de uso.
- **Enlace:** https://playnite.link / https://github.com/JosefNemec/Playnite
---
### LaunchBox (incluye BigBox)
- **Resumen:** Frontend para Windows con versión gratuita y Premium (BigBox) enfocada a experiencias tipo arcade/cabinet.
- **Licencia:** propietario / no LICENSE público — https://www.launchbox-app.com/
- **Funcionalidades principales:** Import masivo, gestión de metadatos, BigBox UI, soporte para EmuMovies y otros proveedores multimedia.
- **Fuentes de metadata/artwork:** EmuMovies (videos), TheGamesDB, y repos comunitarios.
- **Requisitos:** Windows; cuenta/compra para funcionalidades Premium.
- **Riesgos/limitaciones legales:** Uso de assets con licencias comerciales (EmuMovies); verificar términos de redistribución.
- **Enlace:** https://www.launchbox-app.com/
---
### OpenEmu
- **Resumen:** Frontend/emulador para macOS con enfoque en experiencia nativa y organización automática de la librería.
- **Licencia:** no especificada / requiere verificación manual — https://github.com/OpenEmu/OpenEmu (Nota: Wikipedia indica "BSD" — https://en.wikipedia.org/wiki/OpenEmu)
- **Funcionalidades principales:** Biblioteca automática, plugins de cores, mapping de controladores, screenshots y artwork.
- **Fuentes de metadata/artwork:** Integraciones comunitarias (ej. TheGamesDB) y edición manual.
- **Requisitos:** macOS (Xcode para builds); cores externos.
- **Riesgos/limitaciones legales:** Plataforma macOS únicamente; confirmar licencia antes de integrar código directamente.
- **Enlace:** https://openemu.org / https://github.com/OpenEmu/OpenEmu
---
### EmulationStation
- **Resumen:** Frontend ligero y altamente configurable, popular en Raspberry Pi y RetroPie.
- **Licencia:** MIT — https://github.com/Aloshi/EmulationStation/blob/master/LICENSE.md
- **Funcionalidades principales:** UI temable, scrapers integrados, gamelist.xml para metadatos, configuraciones para múltiples emuladores.
- **Fuentes de metadata/artwork:** Scraper integrado (configurable), TheGamesDB y otras fuentes comunitarias.
- **Requisitos:** Multi-OS (Raspberry Pi / Linux / Windows builds disponibles).
- **Riesgos/limitaciones legales:** Scraping indiscriminado puede violar TOS; siempre respetar orígenes y atribución.
- **Enlace:** http://emulationstation.org / https://github.com/Aloshi/EmulationStation
---
### RetroArch (Libretro)
- **Resumen:** Frontend multiplataforma que ejecuta "cores" (emuladores) mediante la API libretro.
- **Licencia:** GPL3.0 — https://github.com/libretro/RetroArch/blob/master/COPYING
- **Funcionalidades principales:** Emulación por cores, shaders, runahead, netplay, gestión de cores y descargas.
- **Fuentes de metadata/artwork:** Descargador de contenido; ecosistema amplio.
- **Requisitos:** Multi-OS (Windows/macOS/Linux/Android/Consoles).
- **Riesgos/limitaciones legales:** GPLv3; atención al empaquetado de cores con licencias mixtas.
- **Enlace:** https://www.retroarch.com / https://github.com/libretro/RetroArch
---
### ROMVault
- **Resumen:** Herramienta orientada a la verificación y reparación de sets mediante DATs (No-Intro/Redump/TOSEC).
- **Licencia:** no especificada / requiere verificación manual — http://romvault.com/ (repositorio: https://github.com/gjefferyes/RomVault — sin LICENSE detectado)
- **Funcionalidades principales:** Escaneo por DAT, checksums (CRC/MD5/SHA1), reparación (fixing), soporte CHD/zip/7z.
- **Fuentes de metadata/artwork:** DATs de No-Intro/Redump/TOSEC; DatVault como servicio complementario.
- **Requisitos:** Windows principal; compatibilidad CLI en Linux.
- **Riesgos/limitaciones legales:** Herramienta de verificación (no distribución); respetar licencias de DAT y políticas de los repos.
- **Enlace:** http://romvault.com/ / https://github.com/gjefferyes/RomVault
---
### No-Intro / Redump (DATs)
- **Resumen:** Colecciones de DATs/índices usados para verificar integridad de dumps de ROMs y CDs; son bases de referencia comunitarias.
- **Licencia:** no especificada / requiere verificación manual — No-Intro: https://datomatic.no-intro.org/stuff/terms.txt ; Redump: https://redump.org/
- **Funcionalidades principales:** DATs para verificación, históricos y tickets de cambios.
- **Requisitos:** Uso local; descarga de DATs públicas o vía servicios como DatVault.
- **Riesgos/limitaciones legales:** No alojan ROMs ni ayudan a buscarlos; su uso debe ser para verificación y preservación.
- **Enlace:** https://datomatic.no-intro.org/ / https://redump.org/
---
### EmuMovies
- **Resumen:** Proveedor comercial de videos, música y artwork de alta calidad orientado a frontends.
- **Licencia:** servicio comercial / TOS (restricción en redistribución) — https://www.emumovies.com/terms
- **Funcionalidades principales:** Video snaps, intros, boxes y packs optimizados para frontends.
- **Fuentes de metadata/artwork:** Producción propia y contribuciones verificadas por la comunidad.
- **Requisitos:** Cuenta/suscripción para acceso ampliado.
- **Riesgos/limitaciones legales:** Contenido con licencia comercial; revisar términos antes de integrar/redistribuir.
- **Enlace:** https://www.emumovies.com/
---
## Tabla comparativa (resumen)
| Proyecto | Licencia | OS objetivo | Gestión ROMs | Scraper / Metadatos | Emulación | Price tracking | Extensible | Notas legales |
| ---------------- | -------------------------------------------------------------------------------------------------------- | ------------------- | ----------------- | ---------------------- | ----------------- | -------------: | ------------- | ------------------------------------------ |
| Playnite | MIT — https://github.com/JosefNemec/Playnite/blob/master/LICENSE.md | Windows | Import & librería | IGDB (plugins) | Lanza emuladores | No | Sí (plugins) | No distribuye ROMs; depende de APIs |
| LaunchBox | Proprietary (no LICENSE público) — https://www.launchbox-app.com/ | Windows | Import & BigBox | EmuMovies, TheGamesDB | Lanza emuladores | No | Sí | Contenido premium / licencias comerciales |
| OpenEmu | no especificada / requiere verificación manual — https://github.com/OpenEmu/OpenEmu (ver Wikipedia: BSD) | macOS | Library auto | TheGamesDB, manual | Cores integrados | No | Módulos/cores | macOS-only; verificar LICENSE |
| EmulationStation | MIT — https://github.com/Aloshi/EmulationStation/blob/master/LICENSE.md | RPi/Linux/Win | Gamelist.xml | Scraper integrado | Lanza emuladores | No | Theming | Uso comunitario; cuidado con scraping |
| RetroArch | GPL3.0 — https://github.com/libretro/RetroArch/blob/master/COPYING | Multi-OS | UI + Content | Content Downloader | Emulación (cores) | No | Cores | GPL (ver compatibilidades) |
| ROMVault | no especificada / requiere verificación manual — http://romvault.com/ | Windows/Linux (CLI) | Verificación DAT | DATs (No-Intro/Redump) | No | No | Limitado | Foco en integridad; no distribuye ROMs |
| ClrMamePro | no especificada / requiere verificación manual — https://www.clrmamepro.net/ | Windows | Verificación sets | DATs | No | No | Limitado | Documentación dispersa; verificar licencia |
---
### ClrMamePro
- **Resumen:** Herramienta histórica para verificación de sets MAME; orientada a usuarios avanzados.
- **Licencia:** no especificada / requiere verificación manual — https://www.clrmamepro.net/
- **Funcionalidades principales:** Verificación/gestión de sets, renombrado y mirroring de DATs.
## Vacíos y notas de verificación
- Algunos proveedores de pricing/metadata (PriceCharting, MobyGames) requieren registro o acuerdos comerciales para acceso a API completo; no siempre publican límites de uso.
- ROMVault/OpenEmu/No-Intro/Redump y otras herramientas de referencia requieren verificación manual de licencia antes de integrar o redistribuir assets.
---
## Fuentes
- Playnite: https://playnite.link / Repositorio y LICENSE: https://github.com/JosefNemec/Playnite/blob/master/LICENSE.md
- LaunchBox: https://www.launchbox-app.com/
- OpenEmu: https://openemu.org / Repo: https://github.com/OpenEmu/OpenEmu (verificar LICENSE en repo; Wikipedia indica BSD)
- EmulationStation: http://emulationstation.org / LICENSE: https://github.com/Aloshi/EmulationStation/blob/master/LICENSE.md
- RetroArch / Libretro: https://www.retroarch.com/ / LICENSE (GPLv3): https://github.com/libretro/RetroArch/blob/master/COPYING
- ROMVault: http://romvault.com/ (repositorio: https://github.com/gjefferyes/RomVault — sin LICENSE detectado)
- No-Intro / DAT-o-MATIC (Terms): https://datomatic.no-intro.org/stuff/terms.txt
- Redump: https://redump.org/ (política y FAQ)
- EmuMovies (TOS / Registration Terms): https://www.emumovies.com/terms
- TheGamesDB (API + repo): https://api.thegamesdb.net/ / https://github.com/TheGamesDB/TheGamesDBv2/blob/master/LICENSE
## Metadatos
- **Autor:** Quasar (investigación automatizada)
- **Última actualización:** 2026-02-07
- **Fecha verificación:** 2026-02-07
---
**Resumen ejecutivo:** Para un MVP conviene priorizar integraciones con fuentes de licencia clara y permisiva (IGDB, TheGamesDB) y soluciones OSS (Playnite, RetroArch) mientras que la integración de proveedores comerciales (EmuMovies, PriceCharting) exige acuerdos y verificación de TOS.

181
docs/data-model.md Normal file
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

70
docs/lessons-learned.md Normal file
View File

@@ -0,0 +1,70 @@
# Lecciones aprendidas y recomendaciones técnicas
**Lista priorizada de funcionalidades útiles para el MVP** (justificación breve)
1. **Import local y verificación básica (hashes/DATs)** — esencial: permite que el usuario confirme posesión y garantiza integridad. (Crítico)
2. **Enriquecimiento de metadata con fallback en cadena** (IGDB → RAWG → TheGamesDB → ScreenScraper) — mejora cobertura y calidad; reduce fallos por datos incompletos.
3. **Interfaz de biblioteca básica + búsqueda y filtros** — UX central para adopción temprana.
4. **Caching de artwork y metadatos (LRU)** — reduce llamadas a APIs y mejora rendimiento offline.
5. **DAT verification y reporting (NoIntro/Redump)** — importante para usuarios avanzados que quieren sets consistentes.
6. **Campos personalizados (compra, precio, notas)** — capturar historial de propiedad o compras.
7. **Integración opcional de price tracking** (PriceCharting / IsThereAnyDeal / eBay) — útil pero no crítico para primer lanzamiento.
---
**Patrones comunes observados**
- Import local + escaneo incremental (evitar rehash completo en cada arranque).
- Verificación mediante DATs y checksums (CRC/MD5/SHA1) con distinto "nivel" de escaneo (rápido vs. exhaustivo).
- Cadena de fallback para scrapers: API principal → API secundaria → recurso comunitario → fallback manual.
- Cache de artwork con fallback offline y opción de refresco manual.
- Campos personalizados para historial de compra y notas, frecuentemente añadidos por usuarios.
---
**Recomendaciones técnicas (stack y librerías útiles)**
- **Arquitectura:** Backend (Node.js + TypeScript / Python Flask) + Postgres (JSONB para campos flexibles) + Redis para caching y rate-limiting.
- **Checksum / DATs:** usar librerías nativas (Node: `crypto`, Python: `hashlib`) y utilidades (chdman para CHD, libarchive/jszip para archives); para DAT parsing, reusar implementaciones existentes (RomVault/Dat readers) si licencia lo permite.
- **Cache / LRU:** Redis con TTL o librerías LRU (node-lru-cache) para assets en memoria; S3 compatible para almacenamiento de artwork en producción.
- **Queue / Background jobs:** Bull / Sidekiq para trabajos de scraping y actualización masiva.
- **Rate limiting:** Implementar token bucket / backoff y conteo por API (respetar 429 y Retry-After).
- **Herramientas:** `sharp` para procesamiento de imágenes; `axios` / `node-fetch` con retries; `pm2` para procesos en producción.
---
**Puntos a evitar**
- No implementar características que faciliten la piratería (búsqueda/descarga automática de ROMs, enlaces a repos de ROMs).
- Evitar scraping masivo sin acuerdo (puede violar TOS y exponer a bloqueos legales).
- Evitar dependencias propietarias críticas si no se puede negociar un contrato claro.
---
### PoC propuesta
PoC propuesta: Backend mínimo (Node/Fastify) que implemente search IGDB + RAWG y cache LRU de 24h
- 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.
- 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`).
- Nota: incluir endpoints de privacidad en PoC: GET /api/me/export, DELETE /api/me/delete; retención de logs de aceptación = 2 años.
- Métrica y plan de pruebas: desplegar PoC y medir 1 semana para observar 429, latencia y ratio de cache hit/miss; ajustar estrategia de caching y necesidad de acuerdos comerciales.
- Resultado esperado: validar límites reales, estimar coste y decidir si se requieren acuerdos/premium APIs.
**Nota sobre licencias:** Si se incorpora código o herramientas bajo GPL, documentar las implicaciones de redistribución y empaquetado; preferir licencias permisivas (MIT/BSD) para componentes que se redistribuirán o empaquetarán con el servicio. Realizar verificación de compatibilidad de licencias y documentar cualquier restricción (compatibilidad de licencias).
## Fuentes y lecturas recomendadas
- Proyectos principales: Playnite, LaunchBox, OpenEmu, RetroArch (ver sus repos y docs)
- Herramientas de verificación: ROMVault, NoIntro (datomatic), Redump
- APIs y pricing: IGDB, RAWG, TheGamesDB, ScreenScraper, PriceCharting, IsThereAnyDeal
## Metadatos
- **Autor:** Quasar (investigación automatizada)
- **Última actualización:** 2026-02-07
- **Fecha verificación:** 2026-02-07
**Resumen:** Priorizar import local + verificación + metadata (con fallbacks) y construir una arquitectura que respete límites de APIs y licencias; las integraciones de precio/ofertas pueden añadirse en una fase posterior tras validar acuerdos comerciales.

89
docs/requirements.md Normal file
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

79
package-lock.json generated
View File

@@ -1,79 +0,0 @@
{
"name": "quasar",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "quasar",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.58.0"
}
},
"node_modules/@playwright/test": {
"version": "1.58.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.0.tgz",
"integrity": "sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.58.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.58.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz",
"integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.58.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz",
"integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}

View File

@@ -1,6 +1,11 @@
{ {
"name": "quasar", "name": "quasar",
"version": "1.0.0", "version": "1.0.0",
"private": true,
"workspaces": [
"backend",
"frontend"
],
"description": "Quasar es una aplicación web para al gestión de una biblioteca personal de videjuegos. Permite a los usuarios catalogar, organizar y buscar sus juegos de manera eficiente. Se pueden agregar videjuegos físicos, digitales y roms de emuladores.", "description": "Quasar es una aplicación web para al gestión de una biblioteca personal de videjuegos. Permite a los usuarios catalogar, organizar y buscar sus juegos de manera eficiente. Se pueden agregar videjuegos físicos, digitales y roms de emuladores.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@@ -0,0 +1,28 @@
## Phase 1 Complete: Análisis comparativo de proyectos y servicios
TL;DR: Se crearon y completaron cuatro documentos de análisis en `docs/` que resumen proyectos relevantes, APIs públicas y consideraciones legales para el MVP. Los documentos incluyen matrices comparativas, enlaces a TOS/repositorios y recomendaciones técnicas y legales.
**Files created/changed:**
- `docs/competitive-analysis.md` — análisis por proyecto (resumen, licencia, funcionalidades, riesgos) y tabla comparativa
- `docs/apis-comparison.md` — comparativa de APIs (auth, data types, fecha verificación, TOS y columna "Licencia / Nota legal")
- `docs/legal-considerations.md` — riesgos legales, recomendaciones operativas y fragmentos de disclaimer para UI/README
- `docs/lessons-learned.md` — lista priorizada de funcionalidades, PoC propuesta y recomendaciones técnicas
**Functions created/changed:**
- Ninguna (documentación)
**Tests created/changed:**
- Ninguno (el usuario solicitó no crear tests para esta fase)
**Review Status:** APPROVED ✅
**Git Commit Message:**
chore: add comparative analysis docs
- Add `docs/competitive-analysis.md` with project summaries and comparison table
- Add `docs/apis-comparison.md` with API TOS links and license notes
- Add `docs/legal-considerations.md` and `docs/lessons-learned.md` with recommendations and PoC
- Add `Metadatos` block (Autor / Fecha verificación: 2026-02-07 / Última actualización)

View File

@@ -0,0 +1,31 @@
## Phase 2 Complete: Requisitos y diseño técnico
TL;DR: Se documentaron y finalizaron los requisitos funcionales y no funcionales del MVP, el diseño de arquitectura (monorepo, stack propuesto) y el modelo de datos inicial para `Game`, `RomFile`, `Platform`, `Artwork`, `Purchase` y `PriceHistory`.
**Files created/changed:**
- `docs/requirements.md`
- `docs/architecture.md`
- `docs/api-integration.md`
- `docs/data-model.md`
- `plans/gestor-coleccion-plan.md` (plan maestro actualizado)
**Functions created/changed:**
- Ninguna (documentación)
**Tests created/changed:**
- Ninguno (recomendación: añadir tests que verifiquen la presencia y metadatos de los documentos claves si se automatiza la validación de docs en CI)
**Review Status:** APPROVED ✅ (con recomendación menor: añadir `docs/legal-considerations.md` si falta para cubrir riesgos legales antes de integrar scraping o descargas masivas)
**Git Commit Message:**
```
chore(docs): completar Fase 2 — requisitos y arquitectura
- Añade/actualiza `docs/requirements.md`, `docs/architecture.md`, `docs/api-integration.md`, `docs/data-model.md`
- Documenta criterios de aceptación y decisiones técnico-arquitectónicas
- Recomendación: añadir `docs/legal-considerations.md` (pendiente)
```

View File

@@ -0,0 +1,237 @@
## 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 + Fastify + TypeScript + Prisma + SQLite (backend).
**Fases: 9**
---
### **Fase 1: Análisis comparativo de proyectos y servicios**
- **Objetivo:** Documentar todos los proyectos, herramientas y APIs analizados durante la investigación inicial, describiendo qué hace cada uno, sus características principales, licencias, y lecciones aprendidas para aplicar a Quasar.
- **Archivos/Funciones a crear/modificar:**
- `docs/competitive-analysis.md` — análisis detallado de proyectos (Playnite, LaunchBox, OpenEmu, EmulationStation, RetroArch, ROMVault, etc.)
- `docs/apis-comparison.md` — comparativa de APIs (IGDB, RAWG, TheGamesDB, Screenscraper, MobyGames, PriceCharting, ITAD, eBay)
- `docs/lessons-learned.md` — patrones y mejores prácticas extraídas del análisis
- **Pasos:**
1. Crear documentos con información estructurada de la investigación inicial
2. Incluir tablas comparativas, enlaces, y conclusiones
3. Documentar patrones útiles y mejores prácticas aplicables a Quasar
---
### **Fase 2: Requisitos y diseño técnico**
- **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)
- `docs/api-integration.md` — descripción de APIs públicas a usar, endpoints, rate limits, autenticación
- `docs/data-model.md` — entidades (Game, RomFile, Platform, Purchase, Artwork)
- **Pasos:**
1. Crear documentos `docs/requirements.md`, `docs/architecture.md`, `docs/api-integration.md`, `docs/data-model.md` con contenido inicial
2. Definir estructura de carpetas y convenciones de código
3. Documentar decisiones técnicas y justificaciones
---
### **Fase 3: Backend base y modelo de datos**
- **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 (fastify, prisma, @fastify/cors, dotenv, etc.)
- `backend/tsconfig.json` — configuración TypeScript backend
- `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`
- **Tests a escribir:**
- `backend/tests/server.spec.ts` — test del servidor (inicia y responde en `/api/health`)
- `backend/tests/models/game.spec.ts` — validaciones del modelo Game (TDD)
- `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 Fastify + Prisma, definir schema, ejecutar migración
3. Implementar endpoint `/api/health`
4. Ejecutar tests y verificar que pasan
---
### **Fase 4: Importadores y gestión de ROMs**
- **Objetivo:** Implementar servicio para escanear directorios locales, calcular checksums (CRC32/MD5/SHA1), detectar formatos (ZIP/7z/CHD), y almacenar en BD. Incluir soporte básico para DAT verification (No-Intro/Redump).
- **Archivos/Funciones a crear/modificar:**
- `backend/src/services/fsScanner.ts` — función `scanDirectory(path: string)`
- `backend/src/services/checksumService.ts` — funciones `calculateCRC32()`, `calculateMD5()`, `calculateSHA1()`
- `backend/src/services/datVerifier.ts` — función `verifyAgainstDAT(romFiles, datPath)`
- `backend/src/routes/import.ts` — endpoint `POST /api/import/scan` (body: {path})
- `backend/src/utils/archiveReader.ts` — leer contenido de ZIP/7z/CHD
- **Tests a escribir:**
- `backend/tests/services/fsScanner.spec.ts` — casos: carpeta vacía, carpeta con ROMs, carpeta con subdirectorios
- `backend/tests/services/checksumService.spec.ts` — calcular checksum de archivo fixture
- `backend/tests/services/datVerifier.spec.ts` — verificar ROM válido/inválido contra DAT fixture
- `backend/tests/routes/import.spec.ts` — test E2E de endpoint `/api/import/scan`
- **Pasos:**
1. Crear fixtures (ROMs de prueba, DAT de prueba)
2. Escribir tests que fallen (escaneo de carpeta, checksum, DAT verification)
3. Implementar `fsScanner`, `checksumService`, `datVerifier` mínimos
4. Implementar endpoint `/api/import/scan`
5. Ejecutar tests y verificar que pasan
---
### **Fase 5: Integración con APIs de metadata**
- **Objetivo:** Clientes para IGDB (OAuth Twitch), RAWG (API key), TheGamesDB (API key); lógica de matching heurística (nombre + plataforma), caché local de respuestas para evitar rate limits.
- **Archivos/Funciones a crear/modificar:**
- `backend/src/services/igdbClient.ts``searchGames(query, platform?)`, `getGameById(id)`
- `backend/src/services/rawgClient.ts``searchGames(query)`, `getGameById(id)`
- `backend/src/services/thegamesdbClient.ts``searchGames(query)`, `getGameById(id)`
- `backend/src/services/metadataService.ts``enrichGame(romFile)` (orquesta clientes, fallbacks, matching heurística)
- `backend/src/utils/cache.ts` — caché en memoria o Redis (simple LRU)
- `backend/src/routes/metadata.ts` — endpoints `GET /api/metadata/search?q=...&platform=...`, `POST /api/metadata/enrich/:romFileId`
- **Tests a escribir:**
- `backend/tests/services/igdbClient.spec.ts` — mock de respuestas IGDB, test de OAuth flow, test de búsqueda
- `backend/tests/services/rawgClient.spec.ts` — mock de respuestas RAWG
- `backend/tests/services/metadataService.spec.ts` — casos: match exacto, match parcial, sin match (fallback)
- `backend/tests/routes/metadata.spec.ts` — test E2E de endpoints metadata
- **Pasos:**
1. Escribir tests con mocks (respuestas API simuladas)
2. Implementar clientes (IGDB OAuth, RAWG key, TheGamesDB key) con retry y timeout
3. Implementar `metadataService` con lógica de matching y fallbacks
4. Implementar endpoints REST
5. Ejecutar tests y verificar que pasan
---
### **Fase 6: Frontend base (React + Vite + shadcn/ui)**
- **Objetivo:** Configurar proyecto frontend con Vite, React, TypeScript, Tailwind CSS, shadcn/ui, TanStack Query, TanStack Router. Implementar layout base (navbar, sidebar), rutas (Home, ROMs, Games, Settings) y componentes UI básicos (Button, Card, Table, Dialog de shadcn/ui).
- **Archivos/Funciones a crear/modificar:**
- `frontend/package.json` — dependencias (react, vite, @shadcn/ui, tailwindcss, @tanstack/react-router, @tanstack/react-query)
- `frontend/tsconfig.json` — configuración TypeScript frontend
- `frontend/vite.config.ts` — configuración Vite (proxy a backend, aliases, TanStack Router plugin)
- `frontend/tailwind.config.js` — configuración Tailwind para shadcn/ui
- `frontend/src/main.tsx` — entry point con QueryClientProvider y RouterProvider
- `frontend/src/routes/__root.tsx` — layout raíz con navbar y sidebar
- `frontend/src/routes/index.tsx` — ruta Home
- `frontend/src/routes/roms.tsx` — ruta ROMs
- `frontend/src/routes/games.tsx` — ruta Games
- `frontend/src/components/layout/Navbar.tsx`
- `frontend/src/components/layout/Sidebar.tsx`
- `frontend/src/lib/api.ts` — cliente HTTP base (fetch/axios wrapper)
- `frontend/src/lib/queryClient.ts` — configuración de TanStack Query
- `frontend/src/hooks/useGames.ts` — custom hook con TanStack Query para juegos
- **Tests a escribir:**
- `frontend/tests/App.spec.tsx` — renderizado de rutas (usando Vitest + React Testing Library)
- `frontend/tests/components/Navbar.spec.tsx` — navegación básica
- `tests/e2e/navigation.spec.ts` — E2E con Playwright (navegar entre páginas)
- **Pasos:**
1. Escribir tests que fallen (renderizado de App, navegación E2E)
2. Configurar Vite + React + TypeScript + Tailwind + shadcn/ui + TanStack Query
3. Implementar layout, rutas, páginas vacías, configuración de QueryClient
4. Ejecutar tests y verificar que pasan
---
### **Fase 7: Gestión manual de juegos (frontend + backend)**
- **Objetivo:** CRUD completo para juegos: crear/editar/eliminar juegos manualmente (frontend form con shadcn/ui + TanStack Query), registrar juegos físicos/digitales con campos: nombre, plataforma, precio, condición (Loose/CIB/New), fecha de compra, vendedor, notas.
- **Archivos/Funciones a crear/modificar:**
- `backend/src/routes/games.ts``GET /api/games`, `POST /api/games`, `PUT /api/games/:id`, `DELETE /api/games/:id`
- `backend/src/controllers/gamesController.ts` — lógica de CRUD
- `backend/src/validators/gameValidator.ts` — validación de input (Zod/Joi)
- `frontend/src/routes/games.tsx` — tabla de juegos con acciones (editar, eliminar)
- `frontend/src/components/games/GameForm.tsx` — formulario para crear/editar juego
- `frontend/src/components/games/GameCard.tsx` — card de vista de juego
- `frontend/src/hooks/useGames.ts` — custom hooks con TanStack Query (useGames, useCreateGame, useUpdateGame, useDeleteGame)
- `frontend/src/components/ui/*` — shadcn/ui components (Form, Input, Select, Textarea, DatePicker)
- **Tests a escribir:**
- `backend/tests/routes/games.spec.ts` — CRUD endpoints (casos: crear juego válido, crear juego con datos faltantes, actualizar, eliminar)
- `frontend/tests/routes/games.spec.tsx` — renderizado de lista, acciones
- `frontend/tests/components/GameForm.spec.tsx` — validación de formulario
- `tests/e2e/games-crud.spec.ts` — E2E completo (crear/editar/eliminar juego)
- **Pasos:**
1. Escribir tests que fallen (endpoints CRUD, renderizado de form, E2E CRUD)
2. Implementar backend endpoints y validators
3. Implementar frontend page, form, custom hooks con TanStack Query
4. Ejecutar tests y verificar que pasan
---
### **Fase 8: Integración ROMs + Metadata (UI completa)**
- **Objetivo:** Vista de ROMs escaneados en frontend, botón para scan de directorio, búsqueda/asociación de metadata (UI para seleccionar resultado de IGDB/RAWG con TanStack Query), y vincular ROM con juego. Incluir vista de artwork (covers, screenshots).
- **Archivos/Funciones a crear/modificar:**
- `frontend/src/routes/roms.tsx` — tabla de ROMs escaneados, botón "Scan Directory", acciones (asociar metadata, ver detalles)
- `frontend/src/components/roms/ScanDialog.tsx` — dialog para input de path y scan
- `frontend/src/components/roms/MetadataSearchDialog.tsx` — búsqueda y selección de metadata
- `frontend/src/components/roms/RomCard.tsx` — card con info de ROM + artwork
- `frontend/src/hooks/useRoms.ts` — custom hooks con TanStack Query (useRoms, useScanDirectory, useEnrichMetadata)
- `backend/src/routes/artwork.ts``GET /api/artwork/:gameId` (proxy/cache de imágenes)
- **Tests a escribir:**
- `frontend/tests/routes/roms.spec.tsx` — renderizado de tabla, acciones
- `frontend/tests/components/ScanDialog.spec.tsx` — submit de path, loading state
- `tests/e2e/roms-import.spec.ts` — E2E: scan → ver ROMs → asociar metadata → ver juego enriquecido
- **Pasos:**
1. Escribir tests que fallen (UI de ROMs, scan dialog, E2E import completo)
2. Implementar componentes frontend y custom hooks con TanStack Query
3. Implementar endpoint de artwork (cache/proxy)
4. Ejecutar tests y verificar que pasan
---
### **Fase 9: CI, tests E2E, docs y seguridad**
- **Objetivo:** GitHub Actions para CI (lint, tests unitarios, tests E2E con Playwright), configuración de ESLint/Prettier, docs de uso de APIs (cómo obtener keys), seguridad (env vars, .gitignore actualizado, SECURITY.md), y README completo.
- **Archivos/Funciones a crear/modificar:**
- `.github/workflows/ci.yml` — pipeline (install, lint, test, e2e)
- `.eslintrc.cjs` — ajustar para backend + frontend
- `.prettierrc` — ajustar formatos
- `README.md` — actualizar con: setup, features, screenshots, roadmap
- `SECURITY.md` — políticas de seguridad, reporte de vulnerabilidades
- `docs/API_KEYS.md` — instrucciones para obtener keys de IGDB, RAWG, TheGamesDB
- `.env.example` — template de variables de entorno
- `backend/.env.example`, `frontend/.env.example` — templates específicos
- **Tests a escribir:**
- `tests/e2e/full-flow.spec.ts` — E2E completo end-to-end (scan → enrich → crear juego manual → view)
- Asegurar que CI ejecuta todos los tests y Playwright genera reporte
- **Pasos:**
1. Configurar GitHub Actions workflow
2. Ejecutar pipeline en CI (puede fallar inicialmente)
3. Corregir issues de lint/format/tests
4. Actualizar docs (README, SECURITY, API_KEYS)
5. Ejecutar CI nuevamente y verificar que todo pasa
---
## **Preguntas abiertas resueltas** ✅
1. ✅ App web self-hosted (server + frontend separados)
2. ✅ Frontend: TypeScript + React + Vite + shadcn/ui + TanStack Query + TanStack Router
3. ✅ Solo APIs públicas: IGDB (OAuth Twitch gratuito), RAWG (API key gratuita con atribución), TheGamesDB (API key gratuita)
4. ✅ Sin integración con tiendas (Steam/GOG/PSN) en MVP; dejar preparado para futuro (campo `storeId`, `storePlatform` en modelo Game)
5. ✅ Prioridad: gestión de ROMs de directorio + creación manual de juegos físicos/digitales
---
## **Decisiones técnicas clave** 🔧
- **Monorepo:** `/backend` y `/frontend` en el mismo repo, con workspaces de Yarn
- **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)
- **CI:** GitHub Actions con pipeline: install → lint → test → e2e
- **Seguridad:** API keys en `.env`, no commitear secrets
---
## **Roadmap futuro (fuera del MVP)** 🚀
- Integración con tiendas (Steam/GOG/PSN/Xbox) para import automático de compras
- Price tracking con PriceCharting/IsThereAnyDeal (requiere suscripción/API paga)
- Plugins/extensiones (LaunchBox export, Playnite sync)
- Sincronización en nube (opcional, con cifrado E2E)
- Soporte para multiple usuarios (autenticación/autorización)
- Mobile app (React Native o PWA)

33
tests/docs.spec.ts Normal file
View File

@@ -0,0 +1,33 @@
import { test, expect } from '@playwright/test';
import fs from 'fs/promises';
import path from 'path';
const docsDir = path.join(__dirname, '..', 'docs');
test.describe('Docs content checks', () => {
test('competitive-analysis has Licencia lines and Metadatos', async () => {
const file = await fs.readFile(path.join(docsDir, 'competitive-analysis.md'), 'utf-8');
expect(file).toContain('Licencia:');
expect(file).toContain('Fuentes');
expect(file).toContain('Metadatos');
expect(file).toContain('Autor: Quasar (investigación automatizada)');
expect(file).toContain('Última actualización: 2026-02-07');
});
test('apis-comparison includes Fecha verificación and subscription markers', async () => {
const file = await fs.readFile(path.join(docsDir, 'apis-comparison.md'), 'utf-8');
expect(file).toContain('Fecha verificación: 2026-02-07');
expect(file).toMatch(/PriceCharting|MobyGames|EmuMovies/);
expect(file).toContain('TOS');
});
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/Fastify) que implemente search IGDB + RAWG y cache LRU de 24h'
);
expect(file).toContain('compatibilidad de licencias');
expect(file).toContain('Fuentes');
expect(file).toContain('Metadatos');
});
});

2738
yarn.lock

File diff suppressed because it is too large Load Diff