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)
This commit is contained in:
69
.gitea/workflows/ci.yaml
Normal file
69
.gitea/workflows/ci.yaml
Normal 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
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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
18
.prettierrc.cjs
Normal 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',
|
||||||
|
};
|
||||||
9
backend/.eslintignore
Normal file
9
backend/.eslintignore
Normal 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
28
backend/.eslintrc.cjs
Normal 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: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
37
backend/README.md
Normal file
37
backend/README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Backend — Quasar
|
||||||
|
|
||||||
|
Scaffold mínimo del backend usando Fastify + TypeScript + Prisma (SQLite).
|
||||||
|
|
||||||
|
Pasos rápidos:
|
||||||
|
|
||||||
|
1. Instalar dependencias:
|
||||||
|
|
||||||
|
yarn
|
||||||
|
|
||||||
|
2. Generar el cliente Prisma:
|
||||||
|
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
3. Crear la migración inicial (local):
|
||||||
|
|
||||||
|
npx prisma migrate dev --name init
|
||||||
|
|
||||||
|
4. Ejecutar en modo desarrollo:
|
||||||
|
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
5. Tests:
|
||||||
|
|
||||||
|
yarn test
|
||||||
|
|
||||||
|
Notas:
|
||||||
|
|
||||||
|
- Las migraciones **no** se ejecutaron como parte de este commit. Use `.env.example` como referencia para `DATABASE_URL`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Metadatos:
|
||||||
|
|
||||||
|
Autor: GitHub Copilot
|
||||||
|
|
||||||
|
Última actualización: 2026-02-07
|
||||||
41
backend/package.json
Normal file
41
backend/package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:migrate": "prisma migrate dev --name init",
|
||||||
|
"prisma:studio": "prisma studio",
|
||||||
|
"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": "5.22.0",
|
||||||
|
"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": "5.22.0",
|
||||||
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"typescript": "^5.2.0",
|
||||||
|
"vitest": "^0.31.0"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"autor": "GitHub Copilot",
|
||||||
|
"ultima_actualizacion": "2026-02-07"
|
||||||
|
}
|
||||||
|
}
|
||||||
105
backend/prisma/schema.prisma
Normal file
105
backend/prisma/schema.prisma
Normal 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
24
backend/src/app.ts
Normal 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
29
backend/src/index.ts
Normal 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
|
||||||
|
*/
|
||||||
11
backend/src/plugins/prisma.ts
Normal file
11
backend/src/plugins/prisma.ts
Normal 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
|
||||||
|
*/
|
||||||
11
backend/src/routes/health.ts
Normal file
11
backend/src/routes/health.ts
Normal 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
|
||||||
|
*/
|
||||||
21
backend/tests/server.spec.ts
Normal file
21
backend/tests/server.spec.ts
Normal 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
20
backend/tsconfig.json
Normal 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
19
backend/vitest.config.ts
Normal 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'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -21,7 +21,7 @@ Game ↔ PriceHistory (1‑N)
|
|||||||
- `description` (text) — opcional.
|
- `description` (text) — opcional.
|
||||||
- `releaseDate` (DateTime?) — fecha principal.
|
- `releaseDate` (DateTime?) — fecha principal.
|
||||||
- `igdbId`, `rawgId`, `thegamesdbId` (Int?) — ids externos.
|
- `igdbId`, `rawgId`, `thegamesdbId` (Int?) — ids externos.
|
||||||
- `extra` (Json?) — campos flexibles.
|
- `extra` (String?) — JSON serializado (compatible con SQLite); usar `JSON.parse`/`JSON.stringify` al leer/escribir.
|
||||||
- `createdAt`, `updatedAt`.
|
- `createdAt`, `updatedAt`.
|
||||||
|
|
||||||
### Platform
|
### Platform
|
||||||
@@ -30,7 +30,7 @@ Game ↔ PriceHistory (1‑N)
|
|||||||
|
|
||||||
### RomFile
|
### RomFile
|
||||||
|
|
||||||
- `id`, `path`, `filename`, `checksum` (unique), `size` (int), `format`, `hashes` (Json opcional), `status` (active/missing), `addedAt`, `lastSeenAt`, `gameId`.
|
- `id`, `path`, `filename`, `checksum` (unique), `size` (int), `format`, `hashes` (String? — JSON serializado, ej.: {"crc32":"...","md5":"..."}), `status` (active/missing), `addedAt`, `lastSeenAt`, `gameId`.
|
||||||
|
|
||||||
### Artwork
|
### Artwork
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ Game ↔ PriceHistory (1‑N)
|
|||||||
|
|
||||||
```prisma
|
```prisma
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql" // para dev puede usarse sqlite
|
provider = "sqlite" // usar SQLite para desarrollo; cambiar a postgresql en producción si se necesita JSON nativo
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ model Game {
|
|||||||
romFiles RomFile[]
|
romFiles RomFile[]
|
||||||
artworks Artwork[]
|
artworks Artwork[]
|
||||||
tags Tag[]
|
tags Tag[]
|
||||||
extra Json?
|
extra String? // JSON serializado (usar JSON.parse/JSON.stringify para leer y escribir)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@index([title])
|
@@index([title])
|
||||||
@@ -109,7 +109,7 @@ model RomFile {
|
|||||||
checksum String @unique
|
checksum String @unique
|
||||||
size Int
|
size Int
|
||||||
format String
|
format String
|
||||||
hashes Json?
|
hashes String? // JSON serializado (ej.: {"crc32":"...","md5":"..."})
|
||||||
game Game? @relation(fields: [gameId], references: [id])
|
game Game? @relation(fields: [gameId], references: [id])
|
||||||
gameId String?
|
gameId String?
|
||||||
addedAt DateTime @default(now())
|
addedAt DateTime @default(now())
|
||||||
@@ -141,6 +141,16 @@ model Purchase {
|
|||||||
receiptPath String?
|
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 {
|
model Tag {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
@@ -148,7 +158,7 @@ model Tag {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Nota: para monedas se recomienda almacenar `priceCents` (int) para compatibilidad; si se usa Postgres se puede optar por `Decimal`.
|
> 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
79
package-lock.json
generated
79
package-lock.json
generated
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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": {
|
||||||
|
|||||||
31
plans/gestor-coleccion-plan-phase-2-complete.md
Normal file
31
plans/gestor-coleccion-plan-phase-2-complete.md
Normal 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)
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user