-Open League Manager (OLManager) is a public, GPL-3.0 desktop management game built with Tauri v2, Rust, React, and TypeScript. The project continues the OpenFootManager lineage while focusing on transparent community contribution, maintainable releases, and careful data provenance.
+
-## Project status
+
-OLManager is pre-alpha software. Expect incomplete gameplay systems, evolving save formats, and frequent documentation updates while the project is prepared for public open-source collaboration.
+---
-## License and lineage
+> **Current Status:** Pre-alpha — expect incomplete gameplay systems, evolving save formats, and frequent documentation updates.
+> **Last Updated:** 02-MAY-2026
-This repository is licensed under the GNU General Public License v3.0. See [`LICENSE`](LICENSE).
+---
-Code and assets inherited from OpenFootManager are treated as GPL-3.0-compatible unless a later audit documents otherwise. Third-party datasets, generated caches, and source-derived content such as Leaguepedia data are **not** automatically GPL by inheritance; they require separate provenance, attribution, and redistribution review. See [`docs/DATA_PROVENANCE.md`](docs/DATA_PROVENANCE.md).
+## 1. What is Open League Manager?
+
+**Open League Manager (OLManager)** is a public, GPL-3.0 desktop management game built with **Tauri v2**, **Rust**, **React**, and **TypeScript**. The project continues the OpenFootManager lineage while focusing on transparent community contribution, maintainable releases, and careful data provenance.
+
+- **Cross-platform desktop** — native performance via Tauri v2, runs on Windows, macOS, and Linux
+- **Rust-powered backend** — type-safe, zero-cost abstractions for game simulation and data processing
+- **React + TypeScript frontend** — modern reactive UI with full type coverage
+- **Community-first** — public, transparent development with an issue-first contribution model
+- **Data provenance** — careful tracking of external data and asset sources
+
+**Architecture:** Hybrid Tauri v2 (Rust backend / React-TypeScript frontend), Hexagonal architecture in Rust with domain-driven design.
+
+---
+
+## 2. Architecture
+
+```
+┌────────────────────────────────────────────────────────────────────────────┐
+│ APPLICATION ARCHITECTURE │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────┐ │
+│ │ FRONTEND (React + TypeScript) │ │
+│ │ │ │
+│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────────┐ │ │
+│ │ │ Pages │ │Components │ │ Stores │ │ Lib/Utils │ │ │
+│ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └──────┬───────┘ │ │
+│ │ └──────────────┴──────────────┴───────────────┘ │ │
+│ │ │ Tauri IPC │ │
+│ └──────────────────────────┼──────────────────────────────────────┘ │
+│ │ │
+│ ┌──────────────────────────┼──────────────────────────────────────┐ │
+│ │ BACKEND (Rust) │ │ │
+│ │ ▼ │ │
+│ │ ┌──────────────────────────────────────────────────────────┐ │ │
+│ │ │ Tauri Commands ───► Domain Logic ───► Persistence │ │ │
+│ │ │ (IPC handlers) (crates/) (SQLite/FS) │ │ │
+│ │ └──────────────────────────────────────────────────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────┘ │
+└────────────────────────────────────────────────────────────────────────────┘
+```
+
+The full system overview is documented at [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — including the React/Tauri boundary, Rust crate map, persistence layer, testing strategy, and feature-extension rules.
+
+---
+
+## 3. Technical Requirements
-## Local development checks
+| Technology | Version | Notes |
+|---------------|-------------|----------------------------------------------|
+| Rust | **1.80+** | Edition 2021, required for Tauri v2 builds |
+| Node.js | **20+** | Required for frontend tooling |
+| npm | **10+** | Package manager for frontend dependencies |
+| Tauri CLI | **2.x** | `cargo install tauri-cli --version "^2"` |
-Install dependencies first:
+### Core Dependencies
```bash
-npm ci
+# Rust crates (Cargo.toml)
+tauri = "2"
+serde = "1" # Serialization
+rusqlite = "0.31" # SQLite persistence
+
+# Frontend (package.json)
+react = "^18"
+typescript = "^5.4"
+@tauri-apps/api = "^2"
```
-Run the stable non-production checks used by required PR validation:
+---
+
+## 4. Quick Installation
```bash
+# 1. Install frontend dependencies
npm ci
+
+# 2. Run stable non-production checks
cargo fmt --manifest-path src-tauri/Cargo.toml --check
cargo check --manifest-path src-tauri/Cargo.toml
```
-Broader non-production checks are still useful, but currently tracked as pre-existing runtime/test debt and exposed through manual experimental CI jobs instead of protected-branch requirements:
+Broader non-production checks are also available, currently tracked as pre-existing runtime/test debt and exposed through manual experimental CI jobs:
```bash
npm test
@@ -37,16 +118,91 @@ cargo clippy --manifest-path src-tauri/Cargo.toml --workspace --all-targets -- -
cargo test --manifest-path src-tauri/Cargo.toml --workspace
```
-Do not run production Tauri bundle builds as part of normal PR validation. Packaging belongs to the release process.
+> Do not run production Tauri bundle builds as part of normal PR validation. Packaging belongs to the release process.
+
+---
+
+## 5. Project Structure
+
+```
+OLManager/
+├── src/ # Frontend (React + TypeScript)
+│ ├── App.tsx # Root application component
+│ ├── main.tsx # Entry point
+│ ├── components/ # UI components
+│ ├── pages/ # Route pages
+│ ├── store/ # State management
+│ └── lib/ # Utilities and helpers
+│
+├── src-tauri/ # Backend (Rust)
+│ ├── Cargo.toml # Rust dependencies
+│ ├── src/ # Tauri commands and setup
+│ └── crates/ # Domain crates
+│ ├── domain/ # Domain models and enums
+│ ├── ofm_core/ # Core game logic
+│ └── ... # Additional crates
+│
+├── docs/ # Documentation
+│ ├── ARCHITECTURE.md # System architecture
+│ ├── GOVERNANCE.md # Branch model and review gates
+│ ├── RELEASE_PROCESS.md # Release workflow
+│ ├── DATA_PROVENANCE.md # External data sources
+│ └── INHERITED_DOCS_AUDIT.md # Documentation audit
+│
+├── README.md # This file
+├── CONTRIBUTING.md # Contribution guidelines
+├── SECURITY.md # Vulnerability reporting
+└── LICENSE # GPL-3.0 license
+```
+
+---
+
+## 6. Code Conventions
+
+### Rust Conventions
+
+- **Crates:** Lowercase with underscores (e.g., `ofm_core`, `player_rating`)
+- **Types:** PascalCase (e.g., `Player`, `TeamComposition`)
+- **Functions/Methods:** snake_case (e.g., `calculate_rating()`)
+- **Enums:** PascalCase variants (e.g., `LolRole::Support`)
+- **Error handling:** Custom error types with `thiserror`
+
+### TypeScript / React Conventions
+
+- **Components:** PascalCase (e.g., `PlayerCard`, `SquadView`)
+- **Hooks:** camelCase with `use` prefix (e.g., `usePlayerData`)
+- **Files:** PascalCase for components, camelCase for utilities
+- **Types:** PascalCase interfaces and type aliases
+
+### Commits
+
+Format: `(): `
+
+```bash
+feat(player): add LolRole assignment
+fix(scouting): correct rating calculation
+refactor(team): replace formation with TeamComposition
+docs(readme): update architecture diagram
+```
+
+---
+
+## 7. License and Lineage
+
+This repository is licensed under the **GNU General Public License v3.0**. See [`LICENSE`](LICENSE).
+
+Code and assets inherited from OpenFootManager are treated as GPL-3.0-compatible unless a later audit documents otherwise. Third-party datasets, generated caches, and source-derived content such as Leaguepedia data are **not** automatically GPL by inheritance; they require separate provenance, attribution, and redistribution review. See [`docs/DATA_PROVENANCE.md`](docs/DATA_PROVENANCE.md).
+
+---
-## Contributing
+## 8. Contributing
-Contributions are issue-first:
+Contributions are **issue-first**:
-1. Open a template-based issue or join Discussions for questions.
-2. Wait for maintainer approval via `status:approved`.
-3. Branch from `development` using `type/lowercase-slug`, for example `fix/ci-labels`.
-4. Open the PR against `development` unless it is a maintainer release or hotfix promotion.
+1. **Open a template-based issue** or join **Discussions** for questions.
+2. **Wait for maintainer approval** via `status:approved`.
+3. **Branch from `development`** using `type/lowercase-slug`, for example `fix/ci-labels`.
+4. **Open the PR against `development`** unless it is a maintainer release or hotfix promotion.
Start with [`CONTRIBUTING.md`](CONTRIBUTING.md), then review:
@@ -57,6 +213,49 @@ Start with [`CONTRIBUTING.md`](CONTRIBUTING.md), then review:
- [`docs/DATA_PROVENANCE.md`](docs/DATA_PROVENANCE.md) — external data and asset provenance requirements.
- [`SECURITY.md`](SECURITY.md) — private vulnerability reporting guidance.
-## Documentation
+---
+
+## 9. Resources
+
+## Rama `QoL-UI-2` — Resumen de cambios
+
+### 🎨 Sidebar (Dashboard)
+- **Escudo del equipo**: reemplazado el logo genérico de la LEC por el escudo del equipo que gestionás
+- **Sin saltos al expandir/colapsar**: altura fija (`h-8 overflow-visible`), texto y botón toggle siempre en DOM ocultos con `max-w-0/max-h-0` y `delay-150`
+- **Cursor pointer** en el escudo cuando el sidebar está colapsado
+- **Botón toggle oculto** en colapsado (el logo funciona como botón para expandir)
+
+### 📸 Fotos de jugadores
+- **ScoutingPlayerSearchCard**: nueva columna Foto con `resolvePlayerPhoto` (soporta IDs `lec-player-{id}`)
+- **YouthAcademyTab**: misma columna de foto agregada
+- **TeamProfileRosterCard**: misma columna de foto agregada
+
+### 🏷️ Iconos de rol (Community Dragon)
+Reemplazados los badges de texto (`SUPPORT`, `MID`, etc.) por iconos Community Dragon en:
+- `ScoutingPlayerSearchCard`
+- `YouthAcademyTab`
+- `TeamProfileRosterCard`
+
+### 🔄 Ordenación por columnas
+- **PlayersListTab**: ordenación por Nacionalidad; eliminada ordenación por Foto
+- **ScoutingPlayerSearchCard**: ordenable por Jugador, Posición, Edad, Equipo, Valor
+- **TransfersTab**: agregadas ordenaciones por Nombre, Posición, Edad, Equipo, Estado
+- **PlayersListTab**: columna Estado ordenable (préstamo > fichaje > lesionado > normal)
+
+### 🏟️ Modal de confirmación de partido
+- **DashboardMatchConfirmModal**: muestra escudos de los equipos junto a los nombres
+
+### 🔧 Fixes
+- **V43 migration** (`bans_json` column) sincronizada de `feat/champion-stats` a `develop`
+- **Football→LoL position mapping**: corregido en TacticsTab, TeamSelection, NextMatchDisplay, draftResultSimulator
+
+---
+- **Repository:** [github.com/NicoRuedaA/OLManager](https://github.com/NicoRuedaA/OLManager)
+- **Documentation index:** [`docs/README.md`](docs/README.md)
+- **Tauri v2 Docs:** [https://v2.tauri.app/](https://v2.tauri.app/)
+- **Rust Docs:** [https://doc.rust-lang.org/](https://doc.rust-lang.org/)
+- **React Docs:** [https://react.dev/](https://react.dev/)
+
+---
-The main documentation index is [`docs/README.md`](docs/README.md).
+Built with Rust + Tauri + React + TypeScript + Community + Passion
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 382fc2475..e5a6ac32b 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -6,18 +6,44 @@ OLManager is a desktop game built with **Tauri v2**: a **React + TypeScript** fr
## System overview
-```text
-React UI (src/)
- pages, components, hooks, stores, services
- │
- │ @tauri-apps/api invoke("command_name")
- ▼
-Tauri command layer (src-tauri/src/commands/)
- │
- ├─ application services (src-tauri/src/application/)
- ├─ in-memory session state (ofm_core::state::StateManager)
- ├─ domain/gameplay crates (domain, ofm_core, engine)
- └─ persistence crate (db, SQLite save files)
+```mermaid
+C4Context
+ Person(user, "Player", "Desktop game user managing an esports team")
+
+ System_Boundary(frontend, "WebView (React 19 + TS)") {
+ System(ui, "Pages & Components", "src/pages/, src/components/")
+ System(store, "Zustand stores", "src/store/ (game, settings)")
+ System(svc, "IPC Services", "src/services/ (typed invoke wrappers)")
+ }
+
+ System_Boundary(backend, "Tauri v2 Backend (Rust)") {
+ System(cmd, "Command layer", "src-tauri/src/commands/ (thin handlers)")
+ System(app, "Application services", "src-tauri/src/application/")
+ System(sm, "StateManager", "ofm_core::state (unified Session)")
+ System_db(db, "Persistence", "db crate (SQLite per-save)")
+ }
+
+ System_Boundary(crates, "Rust Crates") {
+ System(domain, "domain", "Model types (Player, Team, etc.)")
+ System(engine, "engine", "Match simulation (pure, no I/O)")
+ System(ofm, "ofm_core", "Gameplay orchestration, turn logic")
+ }
+
+ System_Ext(leaguepedia, "Leaguepedia API", "External data (optional)")
+
+ Rel(ui, store, "reads/writes")
+ Rel(ui, svc, "calls")
+ Rel(svc, cmd, "invoke('cmd', payload)")
+ Rel(cmd, app, "delegates to")
+ Rel(cmd, sm, "reads/writes state")
+ Rel(cmd, db, "loads/saves games")
+ Rel(app, ofm, "orchestrates gameplay")
+ Rel(ofm, engine, "runs simulation")
+ Rel(ofm, domain, "uses types")
+ Rel(db, ofm, "persists/loads domain objects")
+ Rel(ui, leaguepedia, "fetches champion data", "optional")
+
+ UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="2")
```
The frontend should present state, collect user intent, and call typed service functions. The backend owns authoritative game state, simulations, save/load, and mutations that affect the career.
@@ -51,7 +77,7 @@ Use this boundary deliberately:
The backend keeps process-level state with Tauri-managed objects:
-- `ofm_core::state::StateManager` stores the active `Game`, stats state, live match session, and active save id behind mutexes.
+- `ofm_core::state::StateManager` stores the active `Game`, stats state, live match session, and active save id within a single `Mutex` (unified lock — no deadlock risk).
- `SaveManagerState` wraps `db::save_manager::SaveManager` for save listing/loading/saving/deleting.
## Rust workspace and crate responsibilities
diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md
index 7de3262ae..92906b8ab 100644
--- a/docs/RELEASE_PROCESS.md
+++ b/docs/RELEASE_PROCESS.md
@@ -8,7 +8,7 @@ OLManager releases are maintainer-owned and source-first until signing/notarizat
2. Maintainer opens a release PR from `development` to `main`.
3. Release PR verifies versions, changelog, release notes, provenance, and required checks.
4. After merge to `main`, maintainer creates a version tag or runs release dispatch.
-5. Release workflow creates source archive artifacts and checksums.
+5. Release workflow creates source archive artifacts, platform bundles, checksums, and the Tauri updater manifest.
## Release PR checklist
@@ -44,21 +44,35 @@ v0.3.0
## Artifacts
-Initial releases publish source archives and SHA-256 checksums. Platform binaries, signing, notarization, and installer artifacts are intentionally postponed until maintainers configure secrets and document the support matrix.
+Releases publish source archives, SHA-256 checksums, platform bundles, and `latest.json` for the Tauri updater endpoint:
-If unsigned binaries are ever published, release notes must clearly say they are unsigned and explain the expected verification path.
+```text
+https://github.com/OpenLeagueManager/OLManager/releases/latest/download/latest.json
+```
+
+Updater releases require Ed25519 signatures generated by `tauri-plugin-updater`; unsigned bundles are not valid updater inputs. Installer-level OS signing/notarization is still separate and can be added later when maintainers configure those certificates.
## Hotfixes
Hotfixes may branch from `main` and target `main` only when the issue cannot wait for normal `development` promotion. After the hotfix release, back-merge `main` into `development` immediately.
-## Signing and notarization placeholders
+## Update signing
+
+OLManager uses `tauri-plugin-updater` with Ed25519 bundle signing to verify update integrity.
+
+Required repository secrets:
+
+- `TAURI_SIGNING_PRIVATE_KEY` — the private key generated by `tauri signer generate`.
+- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` — optional password protecting the private key.
+
+The corresponding public key is embedded in `src-tauri/tauri.conf.json` under `plugins.updater.pubkey`. The release workflow signs bundles during `npm run tauri build`, pairs each updater artifact with its `.sig`, generates `latest.json`, and uploads it to the GitHub release. If signed updater artifacts are missing, the workflow fails instead of publishing a broken manifest.
+
+## Installer signing and notarization placeholders
-Potential future secrets include:
+Potential future secrets for OS-level trust:
- Apple Developer ID certificate and notarization credentials.
-- Windows signing certificate.
+- Windows code-signing certificate.
- Linux package signing key.
-- GitHub release token permissions.
-Do not add real secret names, credentials, or signing logic until maintainers decide the release policy.
+Do not add real secret names or credentials until maintainers decide the release policy.
diff --git a/docs/SCRIMS.md b/docs/SCRIMS.md
new file mode 100644
index 000000000..d7d8232ce
--- /dev/null
+++ b/docs/SCRIMS.md
@@ -0,0 +1,1146 @@
+# Scrims System
+
+This document tracks how scrims work, what is already implemented, and the staged plan for turning scrims into a playable competitive-preparation loop.
+
+## Product Goal
+
+Scrims must not be an isolated minigame. They should feed the same systems the player already understands:
+
+- Champion mastery and champion pool development.
+- Champion draft comfort, synergy, and preparation scores.
+- Live match execution and preparation signals.
+- Player profiles, visible form, and LoL-facing attributes.
+- Team morale, fatigue, reputation, and staff recommendations.
+
+The desired loop is:
+
+1. Plan the week with Plan A/B/C opponents.
+2. Set the weekly objective so practice has a clear intent.
+3. Resolve requests and play the scrim block.
+4. Generate a report: result, objective focus, issue, practiced champions, quality, morale/fatigue impact.
+5. Let the manager choose a post-scrim response.
+6. Apply consequences to champion mastery, player attributes, draft prep, livegame prep, and profiles.
+7. Close the day with a readable report and longer-term trends.
+
+## Current Implementation
+
+Already implemented:
+
+- Dedicated `Scrims` dashboard page.
+- Weekly scrim volume controlled from Scrims, separate from Training.
+- Optional weekly scrim objective, persisted as `scrim_weekly_objective`.
+- Staff suggestions on the Scrims page based on objective, quality, cancellations, and loss streak.
+- Plan A/B/C opponent planning per weekly slot.
+- Deterministic fallback acceptance for Plan A/B/C.
+- Scrim reputation and weekly cancellation counters.
+- `cancel_todays_scrims` command with reputation cost.
+- Home card showing today's activity and current day phase.
+- Persisted `day_phase` with phases:
+ - `Morning`
+ - `ScrimBlock`
+ - `ReviewBlock`
+ - `TrainingBlock`
+ - `Evening`
+- Non-match days advance by phase before the full day is processed.
+- Recent played scrim reports feed `ChampionDraft` score bonuses for comfort, preparation, and synergy.
+- Recent played scrim reports feed live match runtime through a conservative `lol_scrim_prep` payload.
+- Weekly scrim staff report summarizes record, quality, focus, recurring issue, practiced champion, and recommendation.
+- Weekly report focus/issue/recommendation params are i18n-keyed for localized inbox rendering.
+
+Current limitation:
+
+- Scrims are still mostly resolved inside daily training processing.
+- Phase advancement is partially visual: `ScrimBlock`, `ReviewBlock`, and `TrainingBlock` do not yet independently apply all gameplay consequences.
+- Legacy scrim result data remains thin: opponent, slot, week, win/loss.
+- Enriched scrim reports now exist, but they are still generated from the current daily training flow until `ScrimBlock` is split out.
+- Player profiles now prefer persisted `champion_masteries`, with seed data as fallback.
+- Post-match result screens mention active scrim preparation when it carried into the match.
+
+## Rework Blueprint
+
+This section is the source of truth for the next rework. Do not keep adding UI patches on top of the current mixed state. The main problem is not one broken component; it is that Home, Scrims, Training, and day phases currently infer scrim state from different pieces of data.
+
+The rework goal is simple:
+
+- One derived scrim context.
+- One weekly preparation room.
+- One daily scrim/review flow.
+- Clear rules for what the user can do at each state.
+
+### Core Problem To Fix
+
+Current code often asks questions like:
+
+- Is there a scrim slot today?
+- Is the day phase `ReviewBlock`?
+- Does a report exist?
+- Does the plan have an opponent?
+- Is there a legacy `scrim_slot_result`?
+
+Those questions are implementation details. UI should not independently combine them. UI should receive a clear answer:
+
+```ts
+type ScrimDayState =
+ | "NoScrimToday"
+ | "Planned"
+ | "Confirmed"
+ | "PlayedNeedsReview"
+ | "Reviewed"
+ | "Cancelled";
+```
+
+If a component needs to know whether to show `Cancel Today`, it should ask `canCancel`, not re-derive state from calendar slots and reports. If a component needs to know whether to show review options, it should ask `canReview`, not inspect `day_phase` manually.
+
+### New Derived Contexts
+
+Create two derived context helpers first. Start frontend-only if speed matters, then move/duplicate in backend once stable.
+
+Recommended frontend path:
+
+- `src/lib/scrimContext.ts`
+
+Recommended exports:
+
+```ts
+export interface TodayScrimContext {
+ state: ScrimDayState;
+ slotIndex: number | null;
+ opponentTeamId: string | null;
+ resolvedOpponentTeamId: string | null;
+ objective: ScrimFocus | null;
+ report: ScrimReportData | null;
+ canEditPlan: boolean;
+ canCancel: boolean;
+ canReview: boolean;
+ canViewWeeklyPlan: boolean;
+ primaryAction: "OpenPlan" | "Review" | "Training" | "Schedule" | null;
+}
+
+export interface WeeklyScrimSlotContext {
+ slotIndex: number;
+ weekday: number;
+ label: string;
+ plan: string[];
+ resolvedOpponentTeamId: string | null;
+ report: ScrimReportData | null;
+ status: "Open" | "Locked" | "Played" | "Reviewed" | "Cancelled";
+ canEdit: boolean;
+}
+
+export interface WeeklyScrimContext {
+ weekKey: string;
+ objective: ScrimFocus | null;
+ capacity: number;
+ reputation: number;
+ cancellations: number;
+ played: number;
+ wins: number;
+ losses: number;
+ lossStreak: number;
+ slots: WeeklyScrimSlotContext[];
+ latestReports: ScrimReportData[];
+ staffAdvice: string[];
+}
+```
+
+Required helpers:
+
+```ts
+deriveTodayScrimContext(gameState, team): TodayScrimContext
+deriveWeeklyScrimContext(gameState, team): WeeklyScrimContext
+```
+
+All UI should consume these helpers instead of duplicating scrim derivation logic.
+
+### State Rules
+
+Use these rules consistently:
+
+- `NoScrimToday`: no planned/resolved scrim slot for today. Home should show training/prep, not scrim reputation.
+- `Planned`: there is a scrim slot today, the scrim has not resolved, and `day_phase === "Morning"`. Home may show `Cancel Today` and `OpenPlan`.
+- `Confirmed`: optional future state if request acceptance becomes explicit before playing. Do not add UI for this until backend exposes it.
+- `PlayedNeedsReview`: a report exists today with `post_decision == null`. Home should show review state only; no cancel, no plan editing primary CTA, no scrim reputation pill.
+- `Reviewed`: today's report exists and has `post_decision`. Home should show training/evening state, not review actions.
+- `Cancelled`: today had a scrim slot but it was cancelled. Home should show cancellation/rest/training state, not Scrims CTA.
+
+The `day_phase` is important, but it is not enough by itself. The derived context must combine phase, reports, slots, and cancellation state once.
+
+### Target Screen Structure
+
+#### Home Today Card
+
+Home should be minimal and action-oriented.
+
+Allowed Home states:
+
+- Official match today: show match CTA.
+- Scrim planned and cancellable: show opponent, objective, `OpenPlan`, `Cancel Today`.
+- Scrim played and unresolved: show review summary and review decision CTA/options.
+- Scrim reviewed: show training/preparation follow-up.
+- No scrim: show training/preparation.
+
+Home must not show:
+
+- Scrim reputation when there is no scrim action available.
+- `Cancel Today` after a scrim has resolved.
+- Generic `Scrims` navigation during `ReviewBlock`.
+- Plan A/B/C details; that belongs in the Scrims page.
+
+#### Scrims Page: Weekly Prep Room
+
+The Scrims page should be organized as a preparation room, not a dumping ground.
+
+Recommended sections in order:
+
+1. Week header: objective, capacity, record, next official rival if available.
+2. Staff advice: derived from objective, reports, opponent strength, reputation, cancellations, and fatigue/morale later.
+3. Weekly plan: per-slot cards with Plan A/B/C and resolved state.
+4. Today block: if today has scrim/review, show focused daily action.
+5. Recent reports: compact, readable, actionable.
+6. Weekly report: show latest Sunday summary inside Scrims, not only Inbox.
+
+Avoid placing too much behavior in one card. The current `ScrimPlanningCard` should remain planning-only.
+
+#### Daily Scrim Block
+
+The daily block should answer: what happens today?
+
+Before resolution:
+
+- Opponent or open plan.
+- Objective/focus.
+- Risk: opponent OVR, scrim reputation gap, cancellation cost.
+- Primary action: advance/resolve via normal day flow.
+- Secondary action: cancel if allowed.
+
+After resolution:
+
+- Result.
+- Quality.
+- Issue detected.
+- Practiced champions.
+- Morale/fatigue impact preview.
+
+#### Review Room
+
+Review is the most important gameplay moment. It should be visually separate and decision-oriented.
+
+Each decision card must show tradeoffs:
+
+- `VodReview`: more prep/draft/macro learning, small condition cost.
+- `MentalReset`: morale/condition recovery, less technical growth.
+- `TargetedDrills`: stronger issue/champion mastery progress, condition cost.
+- `PushThrough`: maximum raw learning, fatigue/tilt risk after bad losses.
+
+Acceptance:
+
+- The player should understand why one option is good or bad before clicking.
+- Do not hide all effects behind backend formulas.
+
+#### Weekly Report
+
+The weekly report should be visible in Scrims page and optionally mirrored to Inbox.
+
+It should include:
+
+- Objective.
+- Played/wins/losses/cancellations.
+- Average quality.
+- Main focus.
+- Recurring issue.
+- Most practiced champion.
+- Most benefited player if available.
+- Recommendation for next week.
+- Whether the weekly objective was fulfilled, partially fulfilled, or failed.
+
+### Backend Direction
+
+Current storage can remain compatible, but behavior should move toward explicit contexts.
+
+Near-term backend commands can stay:
+
+- `set_weekly_scrim_objective`
+- `set_weekly_scrim_plans`
+- `set_weekly_scrim_slots`
+- `cancel_todays_scrims`
+- `choose_post_scrim_decision`
+
+Recommended future backend query/command:
+
+```rust
+get_scrim_context() -> ScrimContextResponse
+```
+
+Shape:
+
+```ts
+interface ScrimContextResponse {
+ today: TodayScrimContext;
+ week: WeeklyScrimContext;
+}
+```
+
+Reason:
+
+- Frontend should not permanently own all state derivation.
+- Backend already knows persistence and phase rules.
+- Central context reduces UI bugs caused by re-deriving state in multiple components.
+
+Do not introduce this backend command until the frontend helper is stable and tests prove the desired states.
+
+### Frontend Contract Snapshot (Ready For Backend Parity)
+
+The frontend contract is now stable enough to mirror in backend `get_scrim_context`.
+
+Current stable shape in `src/lib/scrimContext.ts`:
+
+```ts
+interface TodayScrimContext {
+ state: ScrimDayState;
+ slotIndex: number | null;
+ opponentTeamId: string | null;
+ resolvedOpponentTeamId: string | null;
+ objective: ScrimFocus | null;
+ report: ScrimReportData | null;
+ canEditPlan: boolean;
+ canCancel: boolean;
+ canReview: boolean;
+ canViewWeeklyPlan: boolean;
+ hasOfficialMatch: boolean;
+ primaryAction: "OpenPlan" | "Review" | "Training" | "Schedule" | null;
+}
+
+interface WeeklyScrimSlotContext {
+ slotIndex: number;
+ weekday: number;
+ label: string;
+ labelDay: number;
+ labelSuffix: string;
+ plan: string[];
+ resolvedOpponentTeamId: string | null;
+ resultWon: boolean | null;
+ report: ScrimReportData | null;
+ status: "Open" | "Locked" | "Played" | "Reviewed" | "Cancelled";
+ canEdit: boolean;
+}
+
+interface WeeklyScrimContext {
+ weekKey: string;
+ objective: ScrimFocus | null;
+ capacity: number;
+ planned: number;
+ reputation: number;
+ cancellations: number;
+ played: number;
+ wins: number;
+ losses: number;
+ lossStreak: number;
+ avgQuality: number;
+ topFocus: ScrimFocus | null;
+ topIssue: string | null;
+ nextOfficialRivalTeamId: string | null;
+ nextOfficialRivalCompetition: string | null;
+ slots: WeeklyScrimSlotContext[];
+ latestReports: ScrimReportData[];
+}
+```
+
+Recommended backend response to implement later:
+
+```ts
+interface ScrimContextResponse {
+ today: TodayScrimContext;
+ week: WeeklyScrimContext;
+}
+```
+
+Parity notes:
+
+- Keep `WeeklyScrimSlotContext.label` + `labelDay` + `labelSuffix` as data, so UI does not re-derive label semantics.
+- Keep merged Plan A/legacy fallback logic in one place (backend once migrated).
+- Preserve `resultWon` as nullable to distinguish unresolved from played outcomes.
+
+### Implementation Order For Rework
+
+1. Create `src/lib/scrimContext.ts` with `deriveTodayScrimContext` and `deriveWeeklyScrimContext`.
+2. Move date/week/slot helpers out of `HomeTodayPlanCard`, `ScrimsTab`, and `ScrimPlanningCard` into `scrimContext.ts` or a small `scrimSchedule.ts` helper.
+3. Update `HomeTodayPlanCard` to consume `TodayScrimContext` only.
+4. Update `ScrimsTab` to consume `WeeklyScrimContext` only.
+5. Keep `ScrimPlanningCard` focused on editing `WeeklyScrimSlotContext[]`.
+6. Add tests for every `ScrimDayState`.
+7. Add tests for weekly context: no plan, plan A only, Plan A/B/C, played, reviewed, cancelled, past locked slot.
+8. Only after frontend context is stable, consider exposing `get_scrim_context` from Tauri/backend.
+
+### Tests Required Before Calling The Rework Done
+
+Minimum frontend tests:
+
+- `deriveTodayScrimContext` returns `NoScrimToday` when no slot today.
+- Returns `Planned` in `Morning` with unresolved slot.
+- Returns `PlayedNeedsReview` when today's report has no `post_decision`.
+- Returns `Reviewed` when today's report has `post_decision`.
+- Returns no `canCancel` after report exists.
+- Returns no `canEditPlan` for past/resolved slots.
+- Weekly context preserves Plan A/B/C order.
+- Weekly context handles long team names without layout assumptions.
+
+Minimum UI tests:
+
+- Home does not show cancel/reputation during review.
+- Home shows cancel/reputation only for cancellable planned scrim.
+- Scrim planning renders long names without table layout assumptions.
+- Select opens upward when forced/auto near bottom.
+
+Minimum backend tests if backend context is added:
+
+- Context serialization is stable.
+- Existing saves without `scrim_weekly_objective` load correctly.
+- Cancelled scrims do not later generate reports.
+- `process_scrim_block` stays idempotent.
+- `choose_post_scrim_decision` cannot apply twice.
+
+Backend parity tests to add when `get_scrim_context` is introduced:
+
+- `today.state` transitions match frontend helper for:
+ - `NoScrimToday`
+ - `Planned`
+ - `PlayedNeedsReview`
+ - `Reviewed`
+ - `Cancelled`
+- `week.slots[*]` preserves Plan A/B/C order and `canEdit` lock semantics.
+- `week.slots[*].status` parity for `Open/Locked/Played/Reviewed/Cancelled`.
+- `week` summary parity (`planned`, `avgQuality`, `topIssue`, `nextOfficialRivalTeamId`).
+- `labelDay` / `labelSuffix` are stable for duplicate weekday slots (A/B variants).
+
+### UX Rules
+
+- If there is no action, do not show a CTA.
+- If the scrim already happened, do not show cancel.
+- If the user is in review, show review as the primary experience.
+- If a stat does not help the current decision, hide it or move it to Scrims page.
+- Never make Home explain the whole scrim system.
+- Never make the user infer state from phase labels.
+- Names can be long. Layout must use `min-w-0`, truncation, wrapping, or cards instead of rigid tables.
+- Dropdowns near the bottom must open upward or be scroll-safe.
+
+### What To Keep
+
+- `ScrimReport` model.
+- `PostScrimDecision` enum.
+- `scrim_weekly_objective`.
+- Plan A/B/C concept.
+- `lol_scrim_prep` integration.
+- Champion mastery integration.
+- Weekly report concept.
+
+### What To Simplify Or Remove
+
+- Repeated slot/week/day calculations in components.
+- UI that directly checks `day_phase` and reports independently.
+- Home reputation pill except during actionable planning.
+- Generic Scrims navigation from review states.
+- Any new feature that does not connect to `TodayScrimContext` or `WeeklyScrimContext`.
+
+### Definition Of Done
+
+The rework is done when a user can answer these questions without reading implementation details:
+
+- What are we preparing this week?
+- Who are we scrimming and why?
+- What happened today?
+- What decision do I need to make now?
+- What changed because of that decision?
+- What should I do next week?
+
+If the UI cannot answer those questions, the system is still not finished.
+
+## Post-Rework Next Steps (Backend Parity Phase)
+
+Status:
+
+- Step 7: ✅ Done (`get_scrim_context` backend command implemented)
+- Step 8: ✅ Done (Scrims/Home/Schedule switched to backend context with shared fallback hook)
+- Step 9: ✅ Done (backend/frontend parity mapper tests added and fallback flow centralized)
+
+All frontend rework steps are complete. The remaining work is backend parity so frontend can consume one canonical response.
+
+### Step 7 — Implement `get_scrim_context` (Backend Query)
+
+Goal:
+
+- Expose one backend query that returns `today` + `week` contexts with the same semantics as frontend helpers.
+
+Suggested command signature:
+
+```rust
+get_scrim_context() -> ScrimContextResponse
+```
+
+Requirements:
+
+- Return `TodayScrimContext` parity fields used by Home/Scrims.
+- Return `WeeklyScrimContext` parity fields used by Scrims/Schedule.
+- Keep legacy fallback merge behavior for plan/opponent compatibility.
+
+### Step 8 — Frontend Switch To Backend Context
+
+Goal:
+
+- Replace direct `deriveTodayScrimContext` / `deriveWeeklyScrimContext` calls in UI with backend `get_scrim_context` payload.
+
+Requirements:
+
+- Add compatibility fallback: if backend payload missing, use current frontend helper temporarily.
+- Keep UI behavior unchanged (only data source changes).
+
+### Step 9 — Parity Verification + Cleanup
+
+Goal:
+
+- Prove backend context behavior matches current frontend contract, then remove duplicate derivation paths.
+
+Requirements:
+
+- Add backend parity tests listed in this document.
+- Remove frontend-only fallback derivation once parity is proven.
+- Keep one source of truth (backend) and one rendering layer (frontend).
+
+## Gameplay Activation Plan (Mandatory)
+
+This plan exists to ensure scrims are meaningful gameplay (not a passive "continue" flow).
+
+### Step G1 — Review Room With Visible Tradeoffs
+
+Goal:
+
+- Make post-scrim decisions explicitly impactful and understandable.
+
+Deliverables:
+
+- Decision cards for `VodReview`, `MentalReset`, `TargetedDrills`, `PushThrough`.
+- Each card shows:
+ - Benefits
+ - Costs
+ - "When to pick" guidance
+ - Risk level
+- Selected decision shows immediate feedback summary (what changed now).
+
+Acceptance:
+
+- Player can explain why one decision is better than another before clicking.
+- UI communicates both upside and downside for each option.
+
+### Step G2 — Daily Scrim Block Becomes Decision Point
+
+Goal:
+
+- Make "today" a tactical call, not a passive phase transition.
+
+Deliverables:
+
+- Show opponent pressure signal (OVR/reputation gap class: low/medium/high).
+- Show expected learning value (low/medium/high).
+- Show cancellation cost preview before action.
+- Show staff recommendation with explicit rationale.
+
+Acceptance:
+
+- Player sees risk/reward before pressing continue.
+
+### Step G3 — Post-Scrim Feedback Loop
+
+Goal:
+
+- Ensure outcomes feel consequential and legible.
+
+Deliverables:
+
+- Scrim result summary card with:
+ - Result + quality
+ - Issue detected
+ - Practiced champion highlights
+ - Positive/negative impact notes
+- Decision confirmation strip after review choice with immediate effects.
+
+Acceptance:
+
+- Player can identify what improved and what got worse after each scrim day.
+
+### Step G4 — Weekly Closure and Next-Week Guidance
+
+Goal:
+
+- Close the loop with actionable planning guidance.
+
+Deliverables:
+
+- Weekly outcome: objective fulfilled / partial / failed.
+- Main gain + main failure.
+- Recommendation for next week with one concrete action.
+
+Acceptance:
+
+- Weekly report tells player exactly what to do next.
+
+### Execution Order
+
+1. G1 (Review Room)
+2. G2 (Daily Scrim decision signals)
+3. G3 (Immediate feedback)
+4. G4 (Weekly closure)
+
+## Execution Stages E (Current rollout)
+
+### E6 — Validation & Regression Net
+
+Goal:
+
+- Lock the 2/4/6 model and mandatory review flow with tests so future UI/backend changes do not regress gameplay clarity.
+
+Status:
+
+- Completed.
+
+Coverage closed in this stage:
+
+- Fixed weekly slot distribution for 2/4/6 (`[2,2]`, `[2,2,3,3]`, `[2,2,3,3,4,4]`).
+- Normalization behavior for odd/legacy values (1→2, 3→4, 5→6).
+- Weekly context consistency while changing volume (2→6→4).
+- Advance-time blocker for unresolved post-scrim decisions (`blocked_scrim_decision`).
+- Scrims Today Block interactions for manual decision and assistant delegation.
+- PushThrough critical-cost warning visibility under risky context.
+
+Primary test files:
+
+- `src/lib/scrimContext.test.ts`
+- `src/hooks/useAdvanceTime.test.tsx`
+- `src/components/scrims/ScrimsTab.interaction.test.tsx`
+- `src/components/home/HomeTodayPlanCard.test.tsx`
+- `src/services/trainingService.test.ts`
+
+### E7 — Assistant Automation (Started)
+
+Goal:
+
+- Reduce friction in mandatory review flow by allowing assistant-managed resolution when the user opts in.
+
+Status:
+
+- In progress (vertical slice 1 + settings exposure).
+
+Implemented in this slice:
+
+- New app setting `scrim_review_mode: "manual" | "assistant"` (default `manual`).
+- `useAdvanceTime` now receives `scrim_review_mode` from Dashboard settings.
+- When `advance_time_with_mode` returns `blocked_scrim_decision` and mode is `assistant`, frontend:
+ 1. calls `delegate_scrim_decision`,
+ 2. retries `advance_time_with_mode`,
+ 3. only shows blocker if auto-delegation cannot unlock the flow.
+- Added unit coverage for the auto-delegate + retry path.
+- Exposed `scrim_review_mode` in Settings > Gameplay so users can choose Manual vs Assistant behavior.
+- Added explicit in-dashboard info notice when Continue auto-delegates a blocked scrim decision.
+- Added skip-to-match-day parity: when blocked by pending scrim review and mode is Assistant, skip now auto-delegates and retries once.
+
+Next E7 slices:
+
+1. Add i18n keys for new setting label/options and auto-delegation notice in locale bundles.
+2. Consider exposing an audit trail entry in Inbox when assistant auto-resolves review decisions.
+3. Evaluate whether auto-delegation should be restricted to specific phases (e.g., ReviewBlock only) for stricter UX control.
+
+### E8 — ScrimBlock Decision Loop (New)
+
+Goal:
+
+- Move decision gameplay to `ScrimBlock` with explicit A/B block control, so scrims are interactive, consequential, and not passive "continue" spam.
+
+Locked product requirements:
+
+- Weekly volume remains `2 / 4 / 6`.
+- Scrims run as two blocks per day:
+ - 2 scrims: Wednesday A/B
+ - 4 scrims: Wednesday A/B + Thursday A/B
+ - 6 scrims: Wednesday A/B + Thursday A/B + Friday A/B
+- In `ScrimBlock`, show scrim result first (not generic post-review framing).
+- After first block result:
+ - Continue to second block.
+ - Cancel second block and pick response (`VodReview`, `MentalReset`, `TargetedDrills`).
+ - If loss streak / severe loss / loss vs weaker rival, continue path becomes contextual `PushThrough` (higher learning, morale+condition penalty).
+- After second block result:
+ - Give rest of day off (recovery) OR pick response options (`VodReview`, `MentalReset`, `TargetedDrills`, contextual `PushThrough`).
+- No advance allowed until an option is selected (unless assistant mode resolves it).
+
+Status:
+
+- Closed (v1).
+
+Implemented in E8 v1:
+
+1. Context + UI semantics
+ - Added explicit block framing in Home (`Resultado bloque A/B`, `Scrim 1/2` or `2/2`).
+2. ScrimBlock gating
+ - Pending block decisions are handled in `ScrimBlock` flow and no longer depend on generic review framing.
+3. Block-1 behavior
+ - First-block decisions split into:
+ - continue path (`PushThrough` contextual when risk is high),
+ - or cancel-next-block path (`VodReview`, `MentalReset`, `TargetedDrills`).
+4. Backend cancel-next implementation
+ - Choosing `VodReview` / `MentalReset` / `TargetedDrills` on block 1 auto-cancels the next same-day block and applies weekly cancellation + reputation impact.
+5. Assistant parity
+ - Assistant mode supports unblock flows in Continue/Skip and keeps visible notice in dashboard.
+6. Validation
+ - Updated/added tests for block semantics and decision visibility behavior in Home/Scrims.
+
+Follow-up (post-E8):
+
+1. Add explicit backend/UI action for "rest of day" as first-class decision (currently represented through the existing response set, mainly `MentalReset`).
+2. Add deeper integration tests for full A→B day transitions with mixed decision paths.
+
+### E9 — Day-Off Decision + A/B Integration Hardening
+
+Goal:
+
+- Complete block-based day flow by adding an explicit "rest of day" decision and hardening mixed A→B paths.
+
+Status:
+
+- Closed (v1).
+
+Implemented:
+
+1. New explicit decision: `DayOff`
+ - Added to frontend and backend post-scrim decision model.
+ - Available as first-class option on second daily block framing.
+2. Backend validation
+ - `DayOff` is restricted to second daily block context.
+3. Backend behavior
+ - `DayOff` applies strong recovery effects (morale/condition) and reduced technical pressure.
+4. A/B mixed path behavior
+ - First-block non-PushThrough choices (`VodReview`, `MentalReset`, `TargetedDrills`) cancel next same-day block and apply cancellation/reputation impact.
+5. Tests
+ - Added/updated coverage for block semantics and DayOff visibility path in Home.
+
+### E10 — Daily Scrim Flow from Diagram
+
+Goal:
+
+- Rebuild daily scrim behavior around the actual desired loop: select scrims at the beginning of the day, resolve one block, branch on result quality, and never auto-generate the second block before the player chooses what to do.
+
+Status:
+
+- Started.
+
+Locked flow:
+
+1. Start of scrim day: select the day's scrims explicitly.
+ - No random/fallback opponent selection during resolution.
+ - If no opponent is selected, that block is not played.
+2. Resolve block 1 result.
+3. Branch on block 1 result:
+ - Good result:
+ - Offer rest (cancels remaining scrims that day).
+ - Continue to second block.
+ - Bad result:
+ - Push Through (continue to second block, higher learning, morale/condition penalty).
+ - Cancel scrims, then choose response: `VodReview`, `MentalReset`, or `TargetedDrills`.
+4. Resolve block 2 only after a continue/push-through decision.
+5. Branch on block 2 result:
+ - Good result:
+ - Day off / rest.
+ - Bad result:
+ - Day off / rest.
+ - `VodReview`.
+ - `MentalReset`.
+ - `TargetedDrills`.
+
+Hard rules:
+
+- Block 2 must never be generated before the block 1 decision.
+- PushThrough is not a generic review option; it is a block-1 bad-result continue path.
+- The UI must not show both block decisions at once.
+- Result quality (good/bad) drives available actions.
+
+Implementation slices:
+
+1. Stop automatic/fallback opponent resolution during scrim block. ✅
+2. Resolve only the earliest unresolved selected block for the day. ✅
+3. Add explicit daily flow actions and backend commands. ✅
+ - `ContinueToBlock2`
+ - `OfferRest`
+ - `DayOff`
+ - `PushThrough`
+ - `VodReview`
+ - `MentalReset`
+ - `TargetedDrills`
+4. Replace generic review cards with diagram-based action sets. ✅ (Home v1)
+5. Add integration tests proving A before B, no double pending decisions, and visible impact.
+
+## Target Gameplay Model
+
+### Morning
+
+Manager answers: what risk do we take today?
+
+Actions:
+
+- Review today's schedule.
+- Confirm or cancel scrims.
+- Adjust weekly plan before unresolved slots.
+- Read staff recommendation.
+
+Effects:
+
+- Cancelling protects recovery but hurts scrim reputation.
+- Confirmed scrims proceed into `ScrimBlock`.
+
+### ScrimBlock
+
+Manager answers: what actually happened in practice?
+
+Actions:
+
+- Resolve today's scrim requests.
+- Simulate played scrims.
+- Generate a `ScrimReport`.
+
+Effects:
+
+- Result affects weekly record and scrim reputation.
+- Scrim quality and opponent strength affect learning.
+- Practiced champion picks can gain mastery.
+- Issues are detected for review.
+
+### ReviewBlock
+
+Manager answers: how do we respond to what we learned?
+
+Decision options:
+
+- `VodReview`: converts mistakes into macro/draft learning, softer morale damage, lower recovery.
+- `MentalReset`: protects morale and condition, less technical growth.
+- `TargetedDrills`: improves the detected issue and champion comfort, costs fatigue.
+- `PushThrough`: maximizes training volume, risks tilt/fatigue if the scrim went badly.
+
+Effects:
+
+- Stores a post-scrim decision for the day.
+- Modifies later TrainingBlock and live/draft preparation signals.
+
+### TrainingBlock
+
+Manager answers: how does practice shape player development?
+
+Effects:
+
+- Applies normal training.
+- Applies post-scrim modifiers.
+- Updates LoL-facing player attributes conservatively.
+- Applies champion mastery progress for practiced champions.
+
+### Evening
+
+Manager answers: what did the day leave behind?
+
+Effects:
+
+- Advances date.
+- Resets phase to `Morning`.
+- Generates digest/report messages.
+- Updates weekly trends and staff recommendations.
+
+## Data Model Plan
+
+Recommended minimal model:
+
+```rust
+pub struct ScrimReport {
+ pub date: String,
+ pub week_key: String,
+ pub slot_index: u8,
+ pub team_id: String,
+ pub opponent_team_id: String,
+ pub status: ScrimStatus,
+ pub won: Option,
+ pub focus: ScrimFocus,
+ pub issue: Option,
+ pub severity: u8,
+ pub quality: u8,
+ pub player_champion_picks: Vec,
+ pub post_decision: Option,
+}
+
+pub struct ScrimChampionPick {
+ pub player_id: String,
+ pub champion_id: String,
+ pub role: String,
+}
+```
+
+Recommended enums:
+
+```rust
+pub enum ScrimStatus {
+ Pending,
+ Accepted,
+ Rejected,
+ Cancelled,
+ Played,
+}
+
+pub enum ScrimFocus {
+ DraftPrep,
+ ChampionPool,
+ EarlyGame,
+ Teamfighting,
+ Macro,
+ Mental,
+}
+
+pub enum ScrimIssue {
+ DraftGap,
+ LanePressure,
+ ObjectiveSetup,
+ TeamfightExecution,
+ ChampionComfort,
+ Tilt,
+}
+
+pub enum PostScrimDecision {
+ VodReview,
+ MentalReset,
+ TargetedDrills,
+ PushThrough,
+}
+```
+
+## Integration Points
+
+### Champion Mastery
+
+Scrims should call a dedicated mastery function, not reuse official match progression directly.
+
+Reason:
+
+- Official matches should remain the strongest competitive mastery source.
+- Training remains slower but targeted.
+- Scrims sit in the middle: contextual, champion-specific, and affected by quality/review.
+
+Recommended behavior:
+
+- Scrim loss against strong opponent can still generate high learning.
+- `ChampionPool` and `TargetedDrills` increase champion mastery odds.
+- `VodReview` improves macro/draft learning more than raw champion mastery.
+- `MentalReset` lowers learning but protects morale.
+
+Status:
+
+- Implemented: `apply_scrim_mastery_progress` applies conservative, report-quality-based mastery gains from scrim champion picks.
+- Gains are lower than official match progression and are improved by high quality, wins, `TargetedDrills`, `VodReview`, or strong `PushThrough` reports.
+
+### ChampionDraft
+
+Scrims should feed existing draft score concepts:
+
+- `comfort`: recent practice on selected champions.
+- `preparation`: recent prep against the opponent/style.
+- `synergy`: multiple players practiced a related plan.
+- `counter`: only affected when scouting/review specifically identified a draft gap.
+
+Avoid generic hidden bonuses. Draft UI should explain why a pick is more comfortable/prepared.
+
+Status:
+
+- Implemented: `ChampionDraft` reads the last played reports for each side and adds capped bonuses:
+- `comfort`: selected champions recently practiced by the same player.
+- `preparation`: recent played reports against the upcoming opponent, with stronger value for `DraftPrep` or `VodReview`.
+- `synergy`: two or more selected champions were practiced together in a recent scrim report.
+- UI shows a compact `Scrim prep` explanation when these bonuses are active.
+
+### LiveGame
+
+Scrims should feed livegame as a small preparation signal, not a magic win modifier.
+
+Possible payload:
+
+```ts
+lol_scrim_prep: {
+ home: {
+ preparation: number;
+ focus: "Macro" | "Teamfighting" | "EarlyGame" | "Mental" | "ChampionPool" | "DraftPrep";
+ comfortByPlayer: Record;
+ };
+ away: ...;
+}
+```
+
+Examples:
+
+- `Macro`: better objective setup and decisions.
+- `Teamfighting`: slightly better grouped-fight execution.
+- `EarlyGame`: slightly better lane/jungle early setup.
+- `MentalReset`: reduces negative tilt from loss streaks.
+
+Status:
+
+- Implemented: `MatchSimulation` attaches `lol_scrim_prep` to the runtime snapshot.
+- Implemented: Rust sim v2 reads the payload during champion initialization.
+- Effects are deliberately small: preparation and player champion comfort reduce decision jitter and apply narrow execution modifiers based on focus.
+- Implemented: result screens show a compact explanation when scrim prep was active.
+
+### Player Profiles
+
+Player profiles must show persisted `champion_masteries`.
+
+Reason:
+
+- If scrims improve champion mastery but profiles still read static seed data, the player cannot see the consequence.
+- Visible feedback is mandatory for the loop to feel real.
+
+Status:
+
+- Implemented: `PlayerProfile` prefers persisted `gameState.champion_masteries` over seed-only mastery data.
+
+### Player Attributes
+
+Use existing LoL-facing attribute mappings:
+
+- Mechanics: `dribbling`, `agility`.
+- Laning: `shooting`, `positioning`.
+- Teamfighting: `teamwork`, `composure`, `stamina`.
+- Macro: `vision`, `decisions`, `positioning`.
+- Champion pool: `agility`, `passing`, champion mastery.
+- Discipline: `composure`, `decisions`, `leadership`.
+
+Scrim effects should be smaller than official match post-match development and should usually require a review/training decision to become attribute growth.
+
+## Implementation Stages
+
+### Stage 1: Visible Mastery Loop
+
+Goal:
+
+- Make existing persisted champion mastery visible in player profiles.
+
+Status:
+
+- Implemented.
+
+Acceptance:
+
+- Player profile uses `gameState.champion_masteries` for the selected player.
+- Seed data remains fallback for new saves or players with no persisted mastery.
+
+### Stage 2: Enriched Scrim Report
+
+Goal:
+
+- Replace thin W/L slot result with a richer report model.
+
+Acceptance:
+
+- Scrim result stores status, quality, issue, severity, focus, and practiced champion picks.
+- Scrims page and Home can show the report.
+
+Status:
+
+- Implemented as a compatible persistence layer.
+- Reports are generated from the current scrim resolution path.
+- `scrim_slot_results` remains available for legacy UI compatibility.
+
+### Stage 3: ScrimBlock Resolution
+
+Goal:
+
+- Resolve today's scrims during `ScrimBlock`, not at end-of-day training.
+
+Acceptance:
+
+- Advancing into/through `ScrimBlock` generates reports for today's slots.
+- The game waits for `ReviewBlock` before applying the manager response.
+
+Status:
+
+- Partially implemented: entering `ScrimBlock` now resolves today's scrims and creates enriched reports.
+- Scrim resolution is idempotent, so `TrainingBlock`/Evening processing does not duplicate reports or weekly counters.
+- Implemented: `ReviewBlock` now exposes explicit manager response choices for unresolved reports.
+
+### Stage 4: Post-Scrim Decisions
+
+Goal:
+
+- Add `VodReview`, `MentalReset`, `TargetedDrills`, and `PushThrough`.
+
+Acceptance:
+
+- Home shows the latest unresolved scrim report in `ReviewBlock`.
+- Decision is stored and affects TrainingBlock.
+
+Status:
+
+- Implemented as first vertical slice.
+- `VodReview` improves report quality and softens severity, with light condition cost.
+- `MentalReset` restores morale/condition and softens severity.
+- `TargetedDrills` improves report quality with extra condition cost.
+- `PushThrough` maximizes report quality but costs condition and can hurt morale after severe losses.
+- Scrim champion picks now feed `champion_masteries` after the manager chooses a response.
+
+### Stage 5: Draft Preparation
+
+Goal:
+
+- Feed recent scrim prep into ChampionDraft scoring.
+
+Acceptance:
+
+- Draft score reflects recent champion comfort and preparation.
+- UI explains the source of the prep bonus.
+
+Status:
+
+- Implemented as a capped draft score signal from recent played `scrim_reports`.
+
+### Stage 6: LiveGame Preparation
+
+Goal:
+
+- Feed recent scrim prep into live match runtime as conservative execution signals.
+
+Acceptance:
+
+- Runtime receives a `lol_scrim_prep` payload.
+- Effects are small, specific, and visible in explanations/reports.
+
+Status:
+
+- Implemented as a first runtime slice.
+- Pending: expose post-match explanations/reports that mention the active scrim prep signal.
+
+### Stage 7: Weekly Scrim Report
+
+Goal:
+
+- Summarize trends and staff recommendations.
+
+Acceptance:
+
+- Weekly report includes record, reputation changes, recurring issue, best practiced champions, and recommendation.
+
+Status:
+
+- Implemented as an enriched Sunday staff inbox report.
+- Includes played/wins/losses/cancellations, average quality, current loss streak, main focus, recurring issue, most practiced champion, and staff recommendation.
+
+## Current Step
+
+Current implementation step:
+
+- Scrim loop vertical slice is complete through weekly reporting and post-match visibility.
+
+Next intended step:
+
+- Continue tightening copy/localization for generated labels as more scrim report variants are added.
+
+UI ownership note:
+
+- Implemented: the weekly planning card now lives as `ScrimPlanningCard` under `src/components/scrims`.
+- Implemented: post-match scrim prep insight title, summary, details, and focus labels resolve through frontend i18n keys.
+- Implemented: weekly scrim staff recommendations now travel as recommendation i18n keys instead of raw English text.
diff --git a/docs/UI_DECISION_CARDS.md b/docs/UI_DECISION_CARDS.md
new file mode 100644
index 000000000..49c8791f3
--- /dev/null
+++ b/docs/UI_DECISION_CARDS.md
@@ -0,0 +1,95 @@
+# UI Decision Cards
+
+Decision cards are used when the player chooses between gameplay/management options, such as training focus, tactics, or post-scrim actions.
+
+## Goal
+
+Keep decision surfaces visually consistent across Training, Tactics, Scrims, and future management modules.
+
+## Base Style
+
+Decision cards should follow the Training/Tactics pattern:
+
+- Same background plane as the parent module; avoid darker nested panels unless the whole section requires grouping.
+- Separate options with borders, not colored blocks.
+- Use neutral borders by default:
+ - light: `border-gray-200`
+ - dark: `dark:border-navy-600`
+- Use `border-2` for selectable cards.
+- Use neutral hover:
+ - light: `hover:border-gray-300`
+ - dark: `dark:hover:border-navy-500`
+- Reserve `primary` styling for an actual selected/recommended state, not for normal containers.
+- Avoid semantic color noise (`emerald`, `rose`, `amber`) inside decision options unless representing a real alert state.
+
+## Card Content Structure
+
+Each decision card should contain:
+
+1. Optional icon, matching the module style.
+2. Title in heading font, uppercase, bold.
+3. Short description, 1–2 lines.
+4. Impact tags, using the same visual language as Training attribute tags.
+
+Example structure:
+
+```tsx
+
+```
+
+## Impact Tags
+
+Impact tags explain what the decision improves or worsens.
+
+Rules:
+
+- Use compact labels: `Mental +`, `Volumen -`, `Mecánicas +`.
+- Keep tags visually neutral; the `+`/`-` conveys direction.
+- Avoid green/red coloring for normal tradeoffs.
+- Prefer domain language the player already sees elsewhere.
+- Keep each card to 2–4 tags.
+
+Good examples:
+
+- `Mecánicas +`
+- `Champion Pool +`
+- `Fatiga -`
+- `Recuperación +`
+- `Volumen -`
+- `Mental +`
+
+Bad examples:
+
+- Large colored impact panels inside each card.
+- Red/green badges for every positive/negative effect.
+- Long sentences as tags.
+- Cards with a darker background than their parent module.
+
+## Scrims-Specific Guidance
+
+Post-scrim decisions should use the same card pattern as Training focus cards.
+
+Examples:
+
+- `Push Through`: `Volumen +`, `Aprendizaje +`, `Mental -`
+- `Cancelar scrims`: `Recuperación +`, `Riesgo -`, `Volumen -`
+- `VOD Review`: `Análisis +`, `Calidad +`, `Recuperación -`
+- `Mental Reset`: `Mental +`, `Recuperación +`, `Técnica -`
+- `Targeted Drills`: `Issue +`, `Mecánicas +`, `Fatiga -`
+
+## Principle
+
+Decision cards are not alert panels. They are choice surfaces. Use consistent neutral UI first, then communicate tradeoffs through compact tags.
diff --git a/docs/UPDATER_SETUP.md b/docs/UPDATER_SETUP.md
new file mode 100644
index 000000000..3dcc54d79
--- /dev/null
+++ b/docs/UPDATER_SETUP.md
@@ -0,0 +1,295 @@
+# Guía de configuración del Auto-Updater
+
+> **Idioma / Language:** [Español](#español) | [English](#english)
+
+---
+
+
+## Español
+
+Esta guía explica cómo configurar el sistema de actualizaciones automáticas de OLManager basado en `tauri-plugin-updater`.
+
+### 1. Generación del par de claves Ed25519
+
+El updater utiliza firmas criptográficas Ed25519 para verificar la integridad de los paquetes de actualización.
+
+#### Requisitos previos
+
+- Tener instalado el CLI de Tauri:
+ ```bash
+ cargo install tauri-cli
+ ```
+
+#### Generar claves
+
+```bash
+tauri signer generate
+```
+
+El comando te pedirá:
+- **Password** (opcional): protege la clave privada con una contraseña. Anótala, la necesitarás para los secrets de GitHub.
+- **Ruta de salida**: por defecto genera `~/.tauri/olmanager.key` (privada) y muestra la pública por consola.
+
+#### Archivos resultantes
+
+- **Clave privada** (`olmanager.key` o similar): **NUNCA** la subas al repositorio. Guárdala en un gestor de contraseñas seguro.
+- **Clave pública**: cadena codificada en base64 que empieza por `dW50cnVzdGVkIGNvbW1lbnQ6...`. Es la que configuras en la app.
+
+### 2. Configuración en la aplicación
+
+#### 2.1 `src-tauri/tauri.conf.json`
+
+Dentro del objeto raíz, existe el bloque `plugins.updater`:
+
+```json
+{
+ "plugins": {
+ "updater": {
+ "pubkey": "TU_CLAVE_PUBLICA_AQUI",
+ "endpoints": [
+ "https://github.com/OpenLeagueManager/OLManager/releases/latest/download/latest.json"
+ ],
+ "windows": {
+ "installMode": "passive"
+ }
+ }
+ }
+}
+```
+
+**Campos importantes:**
+
+| Campo | Descripción | Cuándo cambiarlo |
+|-------|-------------|------------------|
+| `pubkey` | Clave pública Ed25519 generada con `tauri signer generate` | Al rotar claves o al cambiar de equipo que firma releases |
+| `endpoints` | URL donde el plugin busca el `latest.json` | Si el repositorio cambia de owner/organización o se usa un mirror/CDN |
+| `windows.installMode` | `passive` (silencioso) o `basicUi` (muestra progreso nativo) | Según preferencia de UX en Windows |
+
+#### 2.2 `src-tauri/Cargo.toml`
+
+Asegúrate de que existe la dependencia:
+
+```toml
+[dependencies]
+tauri-plugin-updater = "2"
+```
+
+Y el campo `repository` apunta al repo correcto:
+
+```toml
+repository = "https://github.com/OpenLeagueManager/OLManager"
+```
+
+#### 2.3 `src-tauri/capabilities/default.json`
+
+Añade el permiso:
+
+```json
+"updater:default"
+```
+
+### 3. Secrets de GitHub
+
+Ve a **Settings > Secrets and variables > Actions** del repositorio y añade:
+
+| Secret | Valor | Obligatorio |
+|--------|-------|-------------|
+| `TAURI_SIGNING_PRIVATE_KEY` | Contenido completo de la clave privada (el archivo `.key`) | Sí |
+| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Contraseña usada al generar la clave (si aplica) | No |
+
+**Importante:** las releases pensadas para auto-update deben tener estos secrets configurados. Sin una firma `.sig` válida, el workflow no puede generar un `latest.json` útil para `tauri-plugin-updater` y fallará antes de publicar el manifiesto del updater.
+
+### 4. Cómo funciona el release
+
+El flujo está automatizado en `.github/workflows/release.yml`:
+
+1. **Crear un tag** `v*.*.*` (ej. `v0.3.0`) o ejecutar el workflow manualmente.
+2. **Job `source-release`**: verifica que las versiones estén sincronizadas (`package.json`, `Cargo.toml`, `tauri.conf.json`) y crea el release en GitHub.
+3. **Job `build-tauri`**: compila los bundles para Windows, Linux y macOS. Si los secrets están configurados, firma cada bundle generando archivos `.sig`.
+4. **Job `generate-latest-json`**: descarga los artefactos de las 3 plataformas, extrae las firmas y ensambla `latest.json` subiéndolo al release en `https://github.com/OpenLeagueManager/OLManager/releases/latest/download/latest.json`.
+
+El manifiesto apunta al artefacto que realmente firma Tauri para cada plataforma: `.msi`/`.exe` en Windows, `.AppImage` en Linux y `.app.tar.gz` en macOS. No cambies esas URLs a instaladores no emparejados con su `.sig`, porque el updater rechazará la descarga.
+
+El archivo `latest.json` tiene este formato:
+
+```json
+{
+ "version": "v0.3.0",
+ "notes": "Notas de la release...",
+ "pub_date": "2026-05-01T12:00:00Z",
+ "platforms": {
+ "windows-x86_64": {
+ "signature": "...",
+ "url": "https://github.com/OpenLeagueManager/OLManager/releases/download/v0.3.0/olmanager-0.3.0-windows-setup.exe"
+ },
+ "linux-x86_64": { ... },
+ "darwin-aarch64": { ... }
+ }
+}
+```
+
+### 5. Testing local del updater
+
+Para probar el updater sin hacer releases reales:
+
+1. Genera un par de claves de prueba.
+2. Crea un servidor local que sirva un `latest.json` falso apuntando a un bundle local.
+3. Modifica temporalmente `endpoints` en `tauri.conf.json` para apuntar a `http://localhost:3000/latest.json`.
+4. Ejecuta la app en modo dev y fuerza una comprobación manual desde Settings.
+
+**Recuerda revertir los cambios de `endpoints` antes de commitear.**
+
+### 6. Rotación de claves
+
+Si necesitas rotar el par de claves:
+
+1. Genera un nuevo par con `tauri signer generate`.
+2. Actualiza `pubkey` en `tauri.conf.json`.
+3. Actualiza los secrets `TAURI_SIGNING_PRIVATE_KEY` y `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` en GitHub.
+4. Publica una nueva release; a partir de ahí, todas las actualizaciones usarán la nueva clave.
+
+---
+
+
+## English
+
+This guide explains how to configure OLManager's automatic update system based on `tauri-plugin-updater`.
+
+### 1. Ed25519 Key Pair Generation
+
+The updater uses Ed25519 cryptographic signatures to verify update package integrity.
+
+#### Prerequisites
+
+- Have the Tauri CLI installed:
+ ```bash
+ cargo install tauri-cli
+ ```
+
+#### Generate keys
+
+```bash
+tauri signer generate
+```
+
+The command will ask you for:
+- **Password** (optional): protects the private key with a password. Write it down, you'll need it for GitHub secrets.
+- **Output path**: by default generates `~/.tauri/olmanager.key` (private) and displays the public key in the console.
+
+#### Resulting files
+
+- **Private key** (`olmanager.key` or similar): **NEVER** commit it to the repository. Store it in a secure password manager.
+- **Public key**: base64-encoded string starting with `dW50cnVzdGVkIGNvbW1lbnQ6...`. This is the one you configure in the app.
+
+### 2. Application Configuration
+
+#### 2.1 `src-tauri/tauri.conf.json`
+
+Inside the root object, the `plugins.updater` block exists:
+
+```json
+{
+ "plugins": {
+ "updater": {
+ "pubkey": "YOUR_PUBLIC_KEY_HERE",
+ "endpoints": [
+ "https://github.com/OpenLeagueManager/OLManager/releases/latest/download/latest.json"
+ ],
+ "windows": {
+ "installMode": "passive"
+ }
+ }
+ }
+}
+```
+
+**Important fields:**
+
+| Field | Description | When to change |
+|-------|-------------|----------------|
+| `pubkey` | Ed25519 public key generated with `tauri signer generate` | When rotating keys or changing the team that signs releases |
+| `endpoints` | URL where the plugin looks for `latest.json` | If the repository changes owner/organization or a mirror/CDN is used |
+| `windows.installMode` | `passive` (silent) or `basicUi` (shows native progress) | According to Windows UX preference |
+
+#### 2.2 `src-tauri/Cargo.toml`
+
+Make sure the dependency exists:
+
+```toml
+[dependencies]
+tauri-plugin-updater = "2"
+```
+
+And the `repository` field points to the correct repo:
+
+```toml
+repository = "https://github.com/OpenLeagueManager/OLManager"
+```
+
+#### 2.3 `src-tauri/capabilities/default.json`
+
+Add the permission:
+
+```json
+"updater:default"
+```
+
+### 3. GitHub Secrets
+
+Go to **Settings > Secrets and variables > Actions** in the repository and add:
+
+| Secret | Value | Required |
+|--------|-------|----------|
+| `TAURI_SIGNING_PRIVATE_KEY` | Complete content of the private key file (the `.key` file) | Yes |
+| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password used when generating the key (if applicable) | No |
+
+**Important:** releases intended for auto-update must have these secrets configured. Without a valid `.sig` signature, the workflow cannot generate a useful `latest.json` for `tauri-plugin-updater` and will fail before publishing the updater manifest.
+
+### 4. How the Release Works
+
+The flow is automated in `.github/workflows/release.yml`:
+
+1. **Create a tag** `v*.*.*` (e.g. `v0.3.0`) or run the workflow manually.
+2. **Job `source-release`**: verifies that versions are synchronized (`package.json`, `Cargo.toml`, `tauri.conf.json`) and creates the GitHub release.
+3. **Job `build-tauri`**: compiles bundles for Windows, Linux, and macOS. If secrets are configured, signs each bundle generating `.sig` files.
+4. **Job `generate-latest-json`**: downloads artifacts from all 3 platforms, extracts signatures, and assembles `latest.json` uploading it to the release at `https://github.com/OpenLeagueManager/OLManager/releases/latest/download/latest.json`.
+
+The manifest points to the artifact Tauri actually signs for each platform: `.msi`/`.exe` on Windows, `.AppImage` on Linux, and `.app.tar.gz` on macOS. Do not change those URLs to installers that are not paired with their `.sig`, because the updater will reject the download.
+
+The `latest.json` file has this format:
+
+```json
+{
+ "version": "v0.3.0",
+ "notes": "Release notes...",
+ "pub_date": "2026-05-01T12:00:00Z",
+ "platforms": {
+ "windows-x86_64": {
+ "signature": "...",
+ "url": "https://github.com/OpenLeagueManager/OLManager/releases/download/v0.3.0/olmanager-0.3.0-windows-setup.exe"
+ },
+ "linux-x86_64": { ... },
+ "darwin-aarch64": { ... }
+ }
+}
+```
+
+### 5. Local Updater Testing
+
+To test the updater without making real releases:
+
+1. Generate a test key pair.
+2. Create a local server that serves a fake `latest.json` pointing to a local bundle.
+3. Temporarily modify `endpoints` in `tauri.conf.json` to point to `http://localhost:3000/latest.json`.
+4. Run the app in dev mode and force a manual check from Settings.
+
+**Remember to revert `endpoints` changes before committing.**
+
+### 6. Key Rotation
+
+If you need to rotate the key pair:
+
+1. Generate a new pair with `tauri signer generate`.
+2. Update `pubkey` in `tauri.conf.json`.
+3. Update the `TAURI_SIGNING_PRIVATE_KEY` and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` secrets in GitHub.
+4. Publish a new release; from then on, all updates will use the new key.
diff --git a/docs/adr/ADR-001-sqlite-per-save.md b/docs/adr/ADR-001-sqlite-per-save.md
new file mode 100644
index 000000000..afc48265d
--- /dev/null
+++ b/docs/adr/ADR-001-sqlite-per-save.md
@@ -0,0 +1,35 @@
+# ADR-001: SQLite per-save database
+
+**Status:** Accepted
+**Date:** 2026-05-02
+**Deciders:** OLManager maintainers
+**Tags:** persistence, architecture
+
+## Context
+
+The game needs to persist manager career state — teams, players, staff, fixtures, messages, news, stats — and load it on demand. Two natural approaches exist:
+
+1. A central database with a save slot table
+2. One database file per save
+
+## Decision
+
+Use **one SQLite database file per save** (`saves/.db`). Migrations are applied on each open via `rusqlite-migration`.
+
+## Rationale
+
+- **Isolation:** A corrupt save doesn't affect others. Experimentation (backup, fork, share save files) is trivial.
+- **Simplicity:** No need for a save slot CRUD layer — the filesystem *is* the save index.
+- **Portability:** Save files can be copied, shared, or debugged with any SQLite tool.
+- **Migrations:** Each file independently tracks its schema version via `PRAGMA user_version`. Forward/backward compatibility is per-save, not global.
+
+## Consequences
+
+- Opening a save runs N migrations each time (N = unapplied migrations). Mitigated by caching the database handle via `open_game_db()`.
+- Cross-save queries (e.g., "compare two careers") require opening multiple databases. Not a current requirement.
+- Save index (`save_index.json`) is a separate file — must stay in sync with `.db` files.
+
+## Alternatives considered
+
+- **Central DB with save slots:** Rejected — higher complexity, lower isolation, harder to debug.
+- **JSON files:** Rejected — no query capability, no schema enforcement, harder to migrate.
diff --git a/docs/adr/ADR-002-rust-crates.md b/docs/adr/ADR-002-rust-crates.md
new file mode 100644
index 000000000..e231cc2bf
--- /dev/null
+++ b/docs/adr/ADR-002-rust-crates.md
@@ -0,0 +1,41 @@
+# ADR-002: Internal Rust crates for bounded contexts
+
+**Status:** Accepted
+**Date:** 2026-05-02
+**Deciders:** OLManager maintainers
+**Tags:** architecture, rust, modularity
+
+## Context
+
+The Rust backend needs to separate concerns: game state types, simulation engine, gameplay orchestration, and database persistence. Mixing all in one crate leads to coupling and slow compile times.
+
+## Decision
+
+Organize into four internal crates under `src-tauri/crates/`:
+
+| Crate | Responsibility | Depends on |
+|-------|---------------|------------|
+| `domain` | Pure model types (Player, Team, League, etc.) | Nothing |
+| `engine` | Deterministic match simulation (no I/O) | `domain` |
+| `ofm_core` | Gameplay orchestration, turn logic, season advancement | `domain`, `engine` |
+| `db` | SQLite persistence, migrations, save management | `domain`, `ofm_core` |
+
+The Tauri command layer (`src-tauri/src/commands/`) depends on `ofm_core` + `db` and never depends on `engine` directly.
+
+## Rationale
+
+- **Dependency direction:** `commands → ofm_core → engine → domain` and `commands → db`. No circular dependencies.
+- **Testability:** Each crate can be tested in isolation. `engine` has no I/O — ideal for property-based testing.
+- **Compile time:** Changes in `domain` don't recompile `engine`. Changes in `engine` don't recompile `db`.
+- **Replaceability:** If SQLite is ever replaced, only `db` changes. If the simulation engine is rewritten, only `engine` and potentially `ofm_core` change.
+
+## Consequences
+
+- Crate boundaries must be respected — `commands/` cannot import `engine` directly.
+- Some types are duplicated across crate boundaries (e.g., `LolRole` in `domain::stats` and `engine::LolRole`). These must stay in sync.
+- `ofm_core` is the largest crate and the most likely to need further splitting.
+
+## Alternatives considered
+
+- **Single crate:** Rejected — 77K LOC across 173 files would be unmanageable.
+- **Workspace of micro-crates:** Too granular for a desktop game — four crates hits the right balance.
diff --git a/docs/proposals/111-remove-home-goals/PROPOSAL.md b/docs/proposals/111-remove-home-goals/PROPOSAL.md
new file mode 100644
index 000000000..607754847
--- /dev/null
+++ b/docs/proposals/111-remove-home-goals/PROPOSAL.md
@@ -0,0 +1,90 @@
+# Proposal: Remove `home_goals`/`away_goals` from `MatchReport`
+
+## Intent
+
+`engine::MatchReport` has two pairs of fields that represent the same thing:
+`home_goals`/`away_goals` (always 0 or 1) and `home_wins`/`away_wins`. The
+former are already `#[serde(skip_serializing)]` — pure dead weight. Remove them
+to eliminate the redundancy and stop confusing "goals" terminology in a LoL
+context. The actual kill count is tracked in `TeamStats.kills` / `KillDetail`.
+
+## Scope
+
+### In Scope
+- Remove `home_goals` and `away_goals` from `engine::MatchReport`
+- Update `engine::report::from_events_with_players()` — stop setting them
+- Update `live_match.rs` — stop setting them in the struct literal
+- Update engine `simulation_tests.rs` — replace reads of `.home_goals` /
+ `.away_goals` with `.home_wins` / `.away_wins`
+- Update `ofm_core` test helpers (`empty_report`, `report_with_scorer`,
+ `full_squad_report`, `make_report`) — stop setting them
+- Verify the crate compiles and tests pass
+
+### Out of Scope
+- `domain::league::Score` (has legitimate `home_wins` field with serde aliases)
+- `domain::news::Score` (legitimate score with actual goal counts)
+- `domain::message::MatchScore` (message payload, different struct)
+- `ofm_core::turn::news::MatchResult` (dedicated score struct)
+- `ofm_core::turn::round_summary::RoundScore` / `GameScore`
+- Frontend TypeScript types (`NewsMatchScore`, `RoundResultSummary`, etc.)
+- DB schema / migrations (no persisted data uses these fields since they were
+ already `skip_serializing`)
+
+## Capabilities
+
+### New Capabilities
+None — pure refactor, no new behavior.
+
+### Modified Capabilities
+None — no spec-level behavior changes. This is a struct cleanup, requirements
+don't change.
+
+## Approach
+
+1. **Remove fields** from `MatchReport` struct definition (lines 75-78).
+2. **Remove assignments** in `from_events_with_players()` (lines 292-293).
+3. **Remove assignments** in `live_match.rs` (lines 260-261).
+4. **Replace reads** in engine `simulation_tests.rs`:
+ - `report.home_goals` → `report.home_wins`
+ - `report.away_goals` → `report.away_wins`
+ - `(report.home_goals, report.away_goals)` → `(report.home_wins, report.away_wins)`
+5. **Drop parameters & assignments** in ofm_core test helpers:
+ - `empty_report(home_goals, away_goals)` → only needs one param or just inline value
+ - Same for `report_with_scorer`, `full_squad_report`, `make_report`
+6. **Drop struct-literal fields** in inline `MatchReport { home_goals: ..., away_goals: ... }` in ofm_core tests.
+7. Run `cargo build` and `cargo test` to confirm.
+
+## Affected Areas
+
+| Area | Impact | Description |
+|------|--------|-------------|
+| `engine/src/report.rs` | Modified | Remove 2 fields + 2 constructor lines |
+| `src/application/live_match.rs` | Modified | Remove 2 lines from struct literal |
+| `engine/tests/simulation_tests.rs` | Modified | ~10 locations: replace reads |
+| `ofm_core/tests/turn_tests.rs` | Modified | ~4 helper fn signatures + ~6 inline literals |
+| `ofm_core/src/turn/news.rs` | Modified | ~1 helper fn + ~1 inline literal |
+
+## Risks
+
+| Risk | Likelihood | Mitigation |
+|------|------------|------------|
+| Missed reference somewhere | Low | Compiler catches all uses of removed fields |
+| Deserialization of old data | None | Fields already `#[serde(default, skip_serializing)]` — no data was ever sent |
+| Tests break silently | Low | `cargo test` in engine + ofm_core catches all |
+
+## Rollback Plan
+
+Revert the commit. Simple struct-only change with no migrations, no data loss,
+no serialization changes. Rollback is zero-risk.
+
+## Dependencies
+
+None. Standalone refactor.
+
+## Success Criteria
+
+- [ ] `cargo build` passes in both `engine` and `ofm_core`
+- [ ] All engine tests pass (esp. deterministic, home advantage, scoring tests)
+- [ ] All ofm_core tests pass (news generation, match report application)
+- [ ] Frontend build passes (no TS changes, just verify)
+- [ ] `home_goals` and `away_goals` appear nowhere in `engine::MatchReport`
diff --git a/docs/proposals/111-remove-home-goals/TASKS.md b/docs/proposals/111-remove-home-goals/TASKS.md
new file mode 100644
index 000000000..794344a93
--- /dev/null
+++ b/docs/proposals/111-remove-home-goals/TASKS.md
@@ -0,0 +1,21 @@
+# Tasks: Remove `home_goals`/`away_goals` from `engine::MatchReport`
+
+## Phase 1: Struct Definition
+
+- [ ] 1.1 Remove `home_goals`/`away_goals` fields + `#[serde(default, skip_serializing)]` from `engine/src/report.rs::MatchReport` (lines 75-78)
+- [ ] 1.2 Remove `home_goals: home_wins` / `away_goals: away_wins` from `Self` constructor in `engine/src/report.rs` (lines 292-293)
+
+## Phase 2: Update Consumers
+
+- [ ] 2.1 Remove `home_goals: home_wins` / `away_goals: away_wins` from `src/application/live_match.rs` struct literal (lines 260-261)
+- [ ] 2.2 Replace all 11 `.home_goals` / `.away_goals` reads with `.home_wins` / `.away_wins` in `engine/tests/simulation_tests.rs`
+- [ ] 2.3 Drop `home_goals`/`away_goals` params from `empty_report`, `report_with_scorer`, `full_squad_report` helpers + remove struct fields in `ofm_core/tests/turn_tests.rs` (~8 locations)
+- [ ] 2.4 Remove inline `home_goals`/`away_goals` struct fields from test assertions in `ofm_core/tests/turn_tests.rs` (lines 578, 602)
+- [ ] 2.5 Drop `home_goals`/`away_goals` params from `make_report` helper + remove struct fields in `ofm_core/src/turn/news.rs` (~4 locations)
+
+## Phase 3: Verification
+
+- [ ] 3.1 `cargo build -p engine -p ofm_core` — confirm compilation succeeds
+- [ ] 3.2 `cargo test -p engine` — confirm all simulation tests pass
+- [ ] 3.3 `cargo test -p ofm_core` — confirm all turn/news tests pass
+- [ ] 3.4 `rg "home_goals|away_goals" src-tauri/crates/engine/` — verify zero remaining references in engine crate
diff --git a/docs/proposals/112-set-piece-takers-plan.md b/docs/proposals/112-set-piece-takers-plan.md
new file mode 100644
index 000000000..f7e8cef68
--- /dev/null
+++ b/docs/proposals/112-set-piece-takers-plan.md
@@ -0,0 +1,204 @@
+# Plan #112: Reemplazar SetPieceTakers con LoL Roles
+
+## Estrategia
+
+Eliminar `free_kick_taker`, `corner_taker`, `penalty_taker` (no existen en LoL).
+Conservar solo `captain` (líder de equipo) y opcionalmente `shotcaller` (quien llama objectives).
+
+---
+
+## Fase 1: DB (primero, como pediste)
+
+### 1a. Migration V41 — Renombrar columna `match_roles`
+
+```sql
+-- Añadir nueva columna con el nuevo nombre
+ALTER TABLE teams ADD COLUMN team_roles TEXT NOT NULL DEFAULT '{}';
+
+-- Migrar datos existentes (serde se encarga de ignorar campos extra)
+UPDATE teams SET team_roles = match_roles;
+
+-- Opcional: drop old column (o dejarla y ignorarla)
+-- ALTER TABLE teams DROP COLUMN match_roles; -- SQLite no soporta DROP COLUMN fácil
+```
+
+**En SQLite no se puede hacer `ALTER TABLE DROP COLUMN`** (es una limitación conocida). Alternativas:
+1. **Dejar la columna**: `match_roles` queda como columna muerta, nunca se escribe. 0 riesgo, 0 data loss.
+2. **Recrear la tabla**: CREATE TABLE new + INSERT INTO + DROP TABLE + RENAME. Más riesgoso.
+
+**Recomendación**: Opción 1. La columna `match_roles` queda como legacy, nunca más se escribe. El código solo escribe/lee `team_roles`.
+
+Archivos a tocar:
+- `db/src/sql/v041_team_roles.sql` — nueva migration
+- `db/src/migrations.rs` — agregar V41
+- `db/src/repositories/team_repo.rs` — cambiar `match_roles` → `team_roles` en INSERT/SELECT
+- `db/tests/academy_team_persistence.rs` — actualizar inline SQL
+
+---
+
+## Fase 2: Domain struct
+
+### 2a. Renombrar `MatchRoles` → `TeamRoles`
+
+```rust
+// domain/src/team.rs
+pub struct TeamRoles {
+ pub captain: Option,
+ pub shotcaller: Option, // nuevo: reemplaza free_kick_taker
+}
+```
+
+- Eliminar: `vice_captain`, `penalty_taker`, `free_kick_taker`, `corner_taker`
+- `shotcaller`: el jugador que llama objectives/shots (opcional, futuro)
+
+Archivos a tocar:
+- `domain/src/team.rs` — struct definition + `Team::team_roles` field + Default
+
+---
+
+## Fase 3: DB Repository (ajuste post-domain)
+
+- `team_repo.rs`: `t.match_roles` → `t.team_roles`, `match_roles_json` → `team_roles_json`
+- Tests de roundtrip: actualizar asserts
+
+---
+
+## Fase 4: Engine
+
+### 4a. Renombrar `SetPieceTakers` → `TeamRoles`
+
+```rust
+// engine/src/live_match/mod.rs
+pub struct TeamRoles {
+ pub captain: Option,
+ pub shotcaller: Option,
+}
+```
+
+### 4b. Renombrar fields del snapshot
+
+```rust
+pub home_roles: TeamRoles,
+pub away_roles: TeamRoles,
+```
+
+### 4c. Renombrar/eliminar MatchCommand variants
+
+```rust
+pub enum MatchCommand {
+ SetCaptain { side: Side, player_id: String },
+ SetShotcaller { side: Side, player_id: String },
+ // Eliminar: SetFreeKickTaker, SetCornerTaker, SetPenaltyTaker
+}
+```
+
+Ambos commands siguen siendo no-ops (idempotentes, no afectan simulación).
+
+Archivos a tocar:
+- `engine/src/live_match/mod.rs` — struct, snapshot, MatchCommand, apply_command
+- `engine/src/live_match/snapshot.rs` — inicialización
+- `engine/src/lib.rs` — re-export
+- `engine/tests/live_match_tests.rs` — actualizar tests
+
+---
+
+## Fase 5: ofm_core
+
+### 5a. `auto_select_set_pieces`
+
+Renombrar a `auto_select_team_roles`. Cambiar return type a `(Option, Option)` para `(captain, shotcaller)`.
+
+Actualmente computa captain (leadership+teamwork), penalty (shooting+composure), free_kick (passing+vision), corner (passing+vision).
+Con el rename:
+- `captain` se mantiene igual (leadership+teamwork)
+- `shotcaller` = el mejor en shooting + vision + passing (hereda de free_kick)
+- penalty/corner lógica se elimina
+
+### 5b. `transfers.rs` + `contracts.rs`
+
+Actualizar referencias de `match_roles.*` → `team_roles.*`. Eliminar limpieza de `penalty_taker`, `free_kick_taker`, `corner_taker`.
+
+### 5c. Tests
+
+Actualizar `live_match_manager_tests.rs`:
+- `auto_select_set_pieces_picks_captain` → se mantiene
+- `auto_select_set_pieces_excludes_gk_from_penalty` → eliminar (penalty no existe)
+- `auto_select_set_pieces_prefers_high_shooting_penalty` → eliminar
+- `auto_select_set_pieces_prefers_high_leadership_captain` → se mantiene
+
+---
+
+## Fase 6: Tauri Commands
+
+- `squad.rs`: `set_team_match_roles` → `set_team_roles`. Actualizar JSON keys.
+- `world.rs`: Actualizar seed JSON.
+- `lib.rs`: Actualizar command registrations.
+
+---
+
+## Fase 7: Frontend TypeScript
+
+### 7a. Types
+
+```typescript
+// src/store/types.ts
+export interface TeamRolesData {
+ captain: string | null;
+ shotcaller: string | null;
+}
+
+// src/components/match/types.ts
+export interface TeamRoles {
+ captain: string | null;
+ shotcaller: string | null;
+}
+```
+
+### 7b. Test files (~10 archivos)
+
+Actualizar todos los mocks que construyen `match_roles: { captain: null, vice_captain: null, ... }` → `team_roles: { captain: null, shotcaller: null }`.
+
+---
+
+## Fase 8: Data files
+
+- `lec_world.json`: Actualizar 38 equipos
+- `generate-lec-world.mjs`: Actualizar generador
+
+---
+
+## Fase 9: Docs
+
+- `ROADMAP.md`: Marcar #112 como done
+- Eliminar referencias legacy en `docs/legacy/`
+
+---
+
+## Orden de implementación
+
+```
+DB (V41 migration) → Domain → DB Repo → Engine → ofm_core → Tauri Commands → Frontend TS → Data → Docs
+```
+
+Este orden permite:
+1. DB migration primero (backwards compatible)
+2. Domain struct cambia (base para todo)
+3. DB repo se ajusta al nuevo struct
+4. Engine consume el nuevo struct
+5. ofm_core usa el nuevo domain + engine
+6. Tauri commands conectan
+7. Frontend refleja los cambios
+8. Data files se actualizan al final
+
+## Resumen de archivos (~27 únicos)
+
+| Capa | Archivos |
+|------|----------|
+| DB | 3 (v041, migrations.rs, team_repo.rs, academy test) |
+| Domain | 1 (team.rs) |
+| Engine | 4 (mod.rs, snapshot.rs, lib.rs, tests) |
+| ofm_core | 5 (team_builder, transfers, contracts, world_io, tests) |
+| Tauri | 3 (squad.rs, world.rs, lib.rs) |
+| Frontend | ~10 (types + test files) |
+| Data | 2 (json + mjs) |
+| Docs | 2 |
diff --git a/docs/proposals/51-draft-strategy/design.md b/docs/proposals/51-draft-strategy/design.md
new file mode 100644
index 000000000..d0530e5ce
--- /dev/null
+++ b/docs/proposals/51-draft-strategy/design.md
@@ -0,0 +1,135 @@
+# Design: Replace PlayStyle enum with LoL DraftStrategy
+
+## Technical Approach
+
+Replace the football‑specific `PlayStyle` enum (6 variants) with a LoL‑themed `DraftStrategy` enum (6 new variants) across the entire stack (Rust backend, TypeScript frontend). The change includes renaming the `Team.play_style` field to `draft_strategy` while preserving backward compatibility via serde aliases. All 173+ Rust references and frontend constants/logic will be updated to use the new enum.
+
+## Architecture Decisions
+
+### Decision: Enum Variant Mapping
+
+**Choice**: Map old variants to new ones as follows:
+- Balanced → Balanced
+- Attacking → Aggressive
+- Defensive → Passive
+- Possession → Scaling
+- Counter → CounterPick
+- HighPress → Aggressive (merge with Attacking)
+
+**Alternatives considered**:
+1. Keep HighPress as a separate variant (e.g., `HighPress`).
+2. Create a new variant `HighPress` but rename to `AggressivePress`.
+3. Merge Attacking and HighPress into `Aggressive` but retain different simulation modifiers.
+
+**Rationale**: The DATA_MIGRATION_PLAN.md already defines this mapping, and the frontend simulation treats both Attacking and HighPress identically for jungle start. Merging simplifies the enum and aligns with LoL draft strategy concepts. Simulation modifiers will be adjusted to preserve the stronger HighPress bonuses for Aggressive.
+
+### Decision: Backward Compatibility via Serde
+
+**Choice**: Use `#[serde(alias = "play_style")]` on the `draft_strategy` field and `#[serde(rename = "...")]` on each variant to keep JSON serialization unchanged (old variant names are preserved).
+
+**Alternatives considered**:
+1. Implement custom `Deserialize` that maps old strings to new variants.
+2. Break backward compatibility and require a migration script.
+
+**Rationale**: Serde aliases are lightweight, zero‑cost, and allow existing saves to load without modification. The rename ensures the JSON representation stays the same, so the frontend can continue sending/receiving the old strings until it is updated.
+
+### Decision: Engine Mirror Synchronization
+
+**Choice**: Replace `engine::PlayStyle` with `engine::DraftStrategy` that exactly mirrors the domain enum (same variant names, same serde rename attributes).
+
+**Alternatives considered**:
+1. Keep the engine enum as `PlayStyle` and convert at the boundary.
+2. Use a type alias.
+
+**Rationale**: Having identical enums in both crates eliminates conversion code and prevents drift. The engine already mirrors the domain; we continue that pattern.
+
+## Data Flow
+
+```
+Frontend (TypeScript)
+ ↓ JSON { "play_style": "Attacking" }
+Serde deserialize (alias: draft_strategy, rename: Aggressive)
+Domain Team struct (draft_strategy: DraftStrategy::Aggressive)
+ ↓ conversion in ofm_core/turn
+Engine TeamData (draft_strategy: DraftStrategy::Aggressive)
+ ↓ simulation modifiers
+Match engine (attack/press/defense phases)
+```
+
+## File Changes
+
+| File | Action | Description |
+|------|--------|-------------|
+| `src-tauri/crates/domain/src/team.rs` | Modify | Add `DraftStrategy` enum, rename field, add aliases. |
+| `src-tauri/crates/engine/src/types.rs` | Modify | Replace `PlayStyle` with `DraftStrategy`. |
+| `src-tauri/crates/engine/src/shared.rs` | Modify | Update `play_style_modifier` match arms for new enum. |
+| `src-tauri/crates/ofm_core/src/generator/generation.rs` | Modify | Update `play_style_from_str` mapping. |
+| `src-tauri/crates/ofm_core/src/turn/mod.rs` | Modify | Update conversion from domain to engine enum. |
+| `src-tauri/crates/ofm_core/src/live_match_manager/team_builder.rs` | Modify | Update conversion. |
+| `src-tauri/crates/db/src/repositories/team_repo.rs` | Modify | Update `parse_play_style` mapping. |
+| `src-tauri/crates/ofm_core/**/*.rs` (≈170 other files) | Modify | Replace `PlayStyle` with `DraftStrategy` in imports and usage. |
+| `src/components/match/types.ts` | Modify | Update `PLAY_STYLES` constant with new IDs/labels. |
+| `src/components/match/lol-prototype/engine/simulation.ts` | Modify | Update `style` comparisons and `styleAggro` mapping. |
+| `src/components/tactics/TacticsTab.helpers.ts` | Modify | Update `HighPress` reference. |
+| `src/components/match/helpers.test.ts` | Modify | Update test data strings. |
+| Various test files | Modify | Update test data to use new enum values. |
+
+## Interfaces / Contracts
+
+```rust
+// domain/src/team.rs
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
+pub enum DraftStrategy {
+ #[default]
+ Balanced,
+ #[serde(rename = "Attacking")]
+ Aggressive,
+ #[serde(rename = "Defensive")]
+ Passive,
+ #[serde(rename = "Possession")]
+ Scaling,
+ #[serde(rename = "Counter")]
+ CounterPick,
+ #[serde(rename = "HighPress")]
+ PriorityBans, // note: HighPress maps to Aggressive; PriorityBans is new
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Team {
+ #[serde(alias = "play_style")]
+ pub draft_strategy: DraftStrategy,
+ // ... other fields
+}
+```
+
+```typescript
+// src/components/match/types.ts
+export const PLAY_STYLES = [
+ { id: "Balanced", label: "Balanced" },
+ { id: "Aggressive", label: "Aggressive" },
+ { id: "Passive", label: "Passive" },
+ { id: "Scaling", label: "Scaling" },
+ { id: "CounterPick", label: "Counter Pick" },
+ { id: "PriorityBans", label: "Priority Bans" },
+];
+```
+
+## Testing Strategy
+
+| Layer | What to Test | Approach |
+|-------|-------------|----------|
+| Unit | Enum serialization/deserialization (Rust) | Add serde tests for aliases and renames. |
+| Unit | Mapping functions (`play_style_from_str`) | Update existing tests, add new cases. |
+| Integration | Domain → engine conversion | Update existing integration tests. |
+| Frontend | Simulation modifiers for new enum values | Add Jest tests for `styleAggro` and jungle start. |
+| E2E | Load existing save file | Ensure team draft strategy is correctly mapped. |
+
+## Migration / Rollout
+
+No database migration required; serde aliases handle existing JSON. Frontend must be updated simultaneously with backend to avoid mismatch (both shipped in same release). Feature flag not needed.
+
+## Open Questions
+
+- [ ] Should `PriorityBans` have any simulation effect in this iteration, or be a placeholder?
+- [ ] What numeric modifiers should `Aggressive` receive for defense phase (currently 0.95 from HighPress, 0.93 from Attacking)? Decision: use 0.95 (HighPress) as Aggressive is more aggressive.
+- [ ] Should the frontend label for `CounterPick` be "Counter Pick" or "Counter‑Pick"? Decision: "Counter Pick".
\ No newline at end of file
diff --git a/docs/proposals/51-draft-strategy/proposal.md b/docs/proposals/51-draft-strategy/proposal.md
new file mode 100644
index 000000000..29ad7fd38
--- /dev/null
+++ b/docs/proposals/51-draft-strategy/proposal.md
@@ -0,0 +1,75 @@
+# Proposal: Replace PlayStyle enum with LoL DraftStrategy
+
+## Intent
+
+The current `PlayStyle` enum is football-specific (Attacking, Defensive, Possession, Counter, HighPress). As the game transitions to a League of Legends-themed manager, we need a LoL-appropriate draft strategy enum that reflects competitive LoL concepts. This change will replace `PlayStyle` with `DraftStrategy`, mapping existing values to LoL equivalents and adding a new `PriorityBans` variant. This aligns the domain model with the new thematic direction.
+
+## Scope
+
+### In Scope
+- Add new `DraftStrategy` enum in `domain/src/team.rs` with variants: `Balanced`, `Aggressive`, `Passive`, `Scaling`, `CounterPick`, `PriorityBans`.
+- Rename `Team.play_style` field to `draft_strategy` with serde alias `"play_style"` for backward compatibility.
+- Mirror the enum in `engine/types.rs` (replace `PlayStyle` with `DraftStrategy`).
+- Update all Rust references (173+ matches across `ofm_core`, `engine`, `db`, `commands`).
+- Update frontend `PLAY_STYLES` constant and adjust simulation logic that depends on play style strings.
+- Ensure backward compatibility for existing save files via serde aliases.
+
+### Out of Scope
+- Changing the simulation logic beyond adapting to the new enum (i.e., no rebalancing of modifiers).
+- Adding new UI components for draft strategy selection.
+- Database migrations (future work tracked in DATA_MIGRATION_PLAN.md).
+
+## Capabilities
+
+### New Capabilities
+- ``: Replaces the football-specific play style with LoL draft strategies, affecting match simulation, team tactics, and AI behavior.
+
+### Modified Capabilities
+- ``: The `Team` struct now uses `draft_strategy` instead of `play_style`; existing saves with `play_style` will deserialize correctly via alias.
+
+## Approach
+
+1. **Define `DraftStrategy` enum** with serde serialization that preserves old variant names for backward compatibility (using `#[serde(rename = "...")]`).
+2. **Update `Team` struct**: rename field to `draft_strategy`, add `#[serde(alias = "play_style")]`.
+3. **Update engine mirror**: replace `PlayStyle` in `engine/types.rs` with `DraftStrategy`.
+4. **Update all Rust code**: replace `PlayStyle` with `DraftStrategy`, map old variants to new names (Attacking→Aggressive, Defensive→Passive, Possession→Scaling, Counter→CounterPick, HighPress→Aggressive). Adjust any logic that differentiates between Attacking and HighPress (e.g., in `engine/shared.rs` we will assign Aggressive the combined modifiers of both former variants).
+5. **Update frontend**: replace `PLAY_STYLES` constant with new IDs/labels; update simulation references (`simulation.ts`, `TacticsTab.helpers.ts`) to use new strings; adjust `styleAggro` mapping for `Aggressive`.
+6. **Add serde aliases** for old field and variant names to ensure existing JSON saves load without migration.
+
+## Affected Areas
+
+| Area | Impact | Description |
+|------|--------|-------------|
+| `src-tauri/crates/domain/src/team.rs` | Modified | Add `DraftStrategy` enum, rename field in `Team`. |
+| `src-tauri/crates/engine/src/types.rs` | Modified | Replace `PlayStyle` with `DraftStrategy`. |
+| `src-tauri/crates/engine/src/shared.rs` | Modified | Update `play_style_modifier` match arms for new enum. |
+| `src-tauri/crates/ofm_core/**/*.rs` | Modified | Update imports and usage of `PlayStyle` (≈173 references). |
+| `src-tauri/crates/db/**/*.rs` | Modified | Update deserialization logic. |
+| `src-tauri/src/commands/**/*.rs` | Modified | Update command handlers. |
+| `src/components/match/types.ts` | Modified | Update `PLAY_STYLES` constant. |
+| `src/components/match/lol-prototype/engine/simulation.ts` | Modified | Update `style` comparisons and `styleAggro` mapping. |
+| `src/components/tactics/TacticsTab.helpers.ts` | Modified | Update HighPress reference. |
+
+## Risks
+
+| Risk | Likelihood | Mitigation |
+|------|------------|------------|
+| Breaking existing saves | Medium | Use serde aliases for field and variant names; thorough deserialization tests. |
+| Frontend simulation regression | Medium | Update simulation logic and add unit tests for new enum values. |
+| Missing references (173+ matches) | Low | Use global search/replace with careful review; run full test suite after changes. |
+
+## Rollback Plan
+
+Revert the branch; the change is self-contained and does not affect database schemas. Existing saves that already use the new enum will not load after rollback, but that's acceptable for a pre-release change.
+
+## Dependencies
+
+- None (this is a standalone refactor).
+
+## Success Criteria
+
+- [ ] All Rust code compiles with `DraftStrategy` replacing `PlayStyle`.
+- [ ] Existing save files (with `play_style` field and old variant names) load correctly.
+- [ ] Frontend simulation behaves identically (or with documented adjustments) for all six draft strategies.
+- [ ] All existing tests pass; new tests added for enum mapping.
+- [ ] No references to `PlayStyle` remain in the codebase (except serde aliases).
\ No newline at end of file
diff --git a/docs/proposals/51-draft-strategy/specs/draft-strategy/spec.md b/docs/proposals/51-draft-strategy/specs/draft-strategy/spec.md
new file mode 100644
index 000000000..5c45def16
--- /dev/null
+++ b/docs/proposals/51-draft-strategy/specs/draft-strategy/spec.md
@@ -0,0 +1,63 @@
+# Draft Strategy Specification
+
+## Purpose
+
+Defines the LoL draft strategy enum used by teams to influence match simulation, AI behavior, and tactical decisions. This replaces the football-specific PlayStyle enum.
+
+## Requirements
+
+### Requirement: DraftStrategy Enum
+
+The system MUST define a `DraftStrategy` enum with the following variants: `Balanced`, `Aggressive`, `Passive`, `Scaling`, `CounterPick`, `PriorityBans`.
+
+#### Scenario: Enum serialization and deserialization
+
+- GIVEN a `DraftStrategy` variant
+- WHEN serialized to JSON
+- THEN the output MUST be the variant name as a string (e.g., `"Aggressive"`)
+
+#### Scenario: Backward compatibility with PlayStyle values
+
+- GIVEN a JSON object containing `"play_style": "Attacking"`
+- WHEN deserialized into a `Team` struct
+- THEN the `draft_strategy` field MUST be `Aggressive`
+- AND the same MUST hold for `"HighPress"` mapping to `Aggressive`
+
+#### Scenario: Default variant
+
+- GIVEN a new `Team` instance
+- WHEN no draft strategy is specified
+- THEN the `draft_strategy` field MUST default to `Balanced`
+
+### Requirement: DraftStrategy Mapping
+
+The system MUST map old `PlayStyle` variants to new `DraftStrategy` variants as follows:
+- `Balanced` → `Balanced`
+- `Attacking` → `Aggressive`
+- `Defensive` → `Passive`
+- `Possession` → `Scaling`
+- `Counter` → `CounterPick`
+- `HighPress` → `Aggressive`
+
+#### Scenario: Legacy data migration
+
+- GIVEN a saved game with `play_style` set to any old variant
+- WHEN loaded after the update
+- THEN the team's `draft_strategy` MUST reflect the mapped new variant
+- AND the system MUST function identically (no loss of tactical behavior)
+
+### Requirement: PriorityBans Variant
+
+The system MUST support a `PriorityBans` draft strategy that influences ban phase decisions in match preparation.
+
+#### Scenario: PriorityBans selection
+
+- GIVEN a team with `draft_strategy` set to `PriorityBans`
+- WHEN the match preparation ban phase executes
+- THEN the team MUST prioritize banning opponent's high‑impact champions
+
+#### Scenario: PriorityBans simulation effect
+
+- GIVEN a team with `draft_strategy` set to `PriorityBans`
+- WHEN the match simulation runs
+- THEN the team MUST receive a bonus to ban effectiveness (MAY be implemented as a global modifier)
\ No newline at end of file
diff --git a/docs/proposals/51-draft-strategy/specs/team-tactics/spec.md b/docs/proposals/51-draft-strategy/specs/team-tactics/spec.md
new file mode 100644
index 000000000..0e8278d88
--- /dev/null
+++ b/docs/proposals/51-draft-strategy/specs/team-tactics/spec.md
@@ -0,0 +1,68 @@
+# Team Tactics Specification
+
+## Purpose
+
+Describes how the `Team` struct stores and exposes its draft strategy, ensuring backward compatibility with existing save files and seamless integration with the match simulation engine.
+
+## Requirements
+
+### Requirement: Team Draft Strategy Field
+
+The `Team` struct MUST contain a field named `draft_strategy` of type `DraftStrategy`. The field MUST be serialized as `"draft_strategy"` but MUST also accept `"play_style"` as an alias during deserialization.
+
+#### Scenario: Serialization of new team
+
+- GIVEN a newly created `Team` instance
+- WHEN serialized to JSON
+- THEN the output MUST contain `"draft_strategy": "Balanced"`
+
+#### Scenario: Deserialization of legacy save
+
+- GIVEN a JSON object representing a team with `"play_style": "Possession"`
+- WHEN deserialized into a `Team` struct
+- THEN the `draft_strategy` field MUST be `Scaling`
+- AND the field name in the resulting struct MUST be `draft_strategy`
+
+### Requirement: Engine Mirroring
+
+The engine crate MUST define its own `DraftStrategy` enum that mirrors the domain enum. The engine enum MUST be identical in variant names and serialization behavior.
+
+#### Scenario: Engine type conversion
+
+- GIVEN a domain `DraftStrategy` value
+- WHEN passed to the engine via `TeamData`
+- THEN the engine MUST accept the value without conversion errors
+- AND the simulation MUST apply the correct modifiers for that strategy
+
+### Requirement: Simulation Modifiers
+
+The match simulation engine MUST apply different numeric modifiers based on the team's `draft_strategy`. The mapping of strategy to modifiers MUST be deterministic and documented.
+
+#### Scenario: Aggressive strategy attack phase
+
+- GIVEN a team with `draft_strategy` set to `Aggressive`
+- WHEN the match enters an attack phase for that team
+- THEN the attack modifier MUST be 1.12 (previously Attacking bonus)
+
+#### Scenario: Aggressive strategy press phase
+
+- GIVEN a team with `draft_strategy` set to `Aggressive`
+- WHEN the match enters a press phase for that team
+- THEN the press modifier MUST be 1.20 (previously HighPress bonus)
+
+#### Scenario: Passive strategy defense phase
+
+- GIVEN a team with `draft_strategy` set to `Passive`
+- WHEN the match enters a defense phase for that team
+- THEN the defense modifier MUST be 1.12 (previously Defensive bonus)
+
+### Requirement: Frontend Consistency
+
+The frontend MUST display draft strategy options using the new variant names. The UI labels SHOULD match the variant names (e.g., "Aggressive") but MAY be localized.
+
+#### Scenario: Play style selector
+
+- GIVEN the tactics configuration screen
+- WHEN the user opens the draft strategy dropdown
+- THEN the list MUST include all six `DraftStrategy` variants
+- AND each option MUST use the new variant name as its identifier
\ No newline at end of file
diff --git a/docs/proposals/51-draft-strategy/tasks.md b/docs/proposals/51-draft-strategy/tasks.md
new file mode 100644
index 000000000..5064f8b7d
--- /dev/null
+++ b/docs/proposals/51-draft-strategy/tasks.md
@@ -0,0 +1,40 @@
+# Tasks: Replace PlayStyle enum with LoL DraftStrategy
+
+## Phase 1: Foundation – Enum & Field Definitions
+
+- [ ] 1.1 Add `DraftStrategy` enum to `src-tauri/crates/domain/src/team.rs` with variants `Balanced`, `Aggressive`, `Passive`, `Scaling`, `CounterPick`, `PriorityBans` and serde renames for old variant names (`Attacking`, `Defensive`, `Possession`, `Counter`, `HighPress`).
+- [ ] 1.2 Rename `Team.play_style` field to `draft_strategy` and add `#[serde(alias = "play_style")]` in the same file.
+- [ ] 1.3 Replace `PlayStyle` enum in `src-tauri/crates/engine/src/types.rs` with `DraftStrategy` (mirror of domain enum, same serde renames).
+- [ ] 1.4 Update `src-tauri/crates/engine/src/shared.rs` to use `DraftStrategy` in `play_style_modifier` match arms; decide numeric modifiers for `Aggressive` (use HighPress values for attack and press phases, HighPress defense value for defense phase).
+- [ ] 1.5 Update `src-tauri/crates/engine/src/lib.rs` export to use `DraftStrategy` instead of `PlayStyle`.
+
+## Phase 2: Core Rust References (≈173 matches)
+
+- [ ] 2.1 Update `src-tauri/crates/ofm_core/src/generator/generation.rs` – replace `PlayStyle` import and `play_style_from_str` mapping.
+- [ ] 2.2 Update `src-tauri/crates/ofm_core/src/turn/mod.rs` – replace domain→engine conversion match.
+- [ ] 2.3 Update `src-tauri/crates/ofm_core/src/live_match_manager/team_builder.rs` – replace conversion.
+- [ ] 2.4 Update `src-tauri/crates/db/src/repositories/team_repo.rs` – replace `parse_play_style` mapping.
+- [ ] 2.5 Global search‑and‑replace `PlayStyle` with `DraftStrategy` across all remaining Rust files in `src-tauri/crates/ofm_core/`, `src-tauri/crates/engine/`, `src-tauri/crates/db/`, `src-tauri/src/commands/`.
+- [ ] 2.6 Update any string literals `"Attacking"`, `"Defensive"`, etc., that are used in UI or logging to new variant names (if needed).
+- [ ] 2.7 Ensure all Rust tests compile and adjust test data to use new enum values.
+
+## Phase 3: Frontend Updates
+
+- [ ] 3.1 Update `src/components/match/types.ts` – replace `PLAY_STYLES` constant with new IDs/labels.
+- [ ] 3.2 Update `src/components/match/lol-prototype/engine/simulation.ts` – replace `style === "HighPress"` etc., with `style === "Aggressive"`; adjust `styleAggro` mapping for `Aggressive`.
+- [ ] 3.3 Update `src/components/tactics/TacticsTab.helpers.ts` – replace `HighPress` reference.
+- [ ] 3.4 Update all frontend test files that contain `play_style: "Balanced"` etc., to use new variant names (or keep as is if serde rename ensures backward compatibility – but better to update).
+- [ ] 3.5 Verify that the frontend dropdown renders the new labels correctly.
+
+## Phase 4: Testing & Verification
+
+- [ ] 4.1 Write serde round‑trip tests for `DraftStrategy` (Rust) – ensure `"Attacking"` deserializes to `Aggressive` and serializes back to `"Attacking"`.
+- [ ] 4.2 Update existing integration tests in `src-tauri/crates/engine/tests/` to use new enum.
+- [ ] 4.3 Add frontend unit test for `styleAggro` mapping with new enum values.
+- [ ] 4.4 Run the full test suite (`cargo test` and `npm test`) and fix any failures.
+- [ ] 4.5 Manual test: load an existing save file and verify that team draft strategies are correctly mapped.
+
+## Phase 5: Cleanup
+
+- [ ] 5.1 Remove any leftover `PlayStyle` references (except serde aliases) – verify with grep.
+- [ ] 5.2 Update any documentation that mentions `PlayStyle` (e.g., README, internal docs).
\ No newline at end of file
diff --git a/docs/proposals/FOOTBALL_NATION_REMOVAL.md b/docs/proposals/FOOTBALL_NATION_REMOVAL.md
new file mode 100644
index 000000000..2fffa8c26
--- /dev/null
+++ b/docs/proposals/FOOTBALL_NATION_REMOVAL.md
@@ -0,0 +1,145 @@
+# Plan: Eliminar `football_nation` de domain types y activar V39
+
+**Issue:** #85 (Database Defutbolization)
+**Branch:** `feat/85-remove-football-nation`
+**Migración:** V39 (deshabilitada — SQL listo en `sql/v039_drop_football_nation.sql`)
+
+---
+
+## Contexto
+
+El campo `football_nation` es un legacy de la migración desde OpenFootManager (fútbol → LoL). Fue reemplazado por `nationality_code` + `competitive_region` pero nunca se eliminó de las tablas ni de los tipos domain.
+
+---
+
+## ⚠️ Riesgos
+
+| Riesgo | Mitigación |
+|--------|-----------|
+| V39 recrea tablas (DROP + CREATE) — si falla a mitad, la partida se corrompe | `rusqlite-migration` envuelve cada migración en transacción SQLite. Si falla, hace rollback automático |
+| El conteo de placeholders `?N` en INSERT es fácil de romper | Verificar cada archivo con `cargo build -p db` después de cada cambio |
+| `player_repo.rs` tiene 34 columnas en INSERT — el conteo de params es tedioso | Hacerlo con paciencia, verificando cada columna contra la lista original |
+| `identity_upgrade.rs` es compartido por `save_manager.rs` y `world.rs` | Refactorizar identity_upgrade.rs completo antes de tocar los otros archivos |
+
+---
+
+## Plan de Ejecución
+
+### Paso 1: Identity Upgrade (1 archivo)
+
+**`ofm_core/src/identity_upgrade.rs`** debe ser refactorizado primero porque es el módulo que más referencias tiene y que usan `save_manager.rs` y `world.rs`.
+
+**Cambios:**
+- Eliminar todas las referencias a `football_nation`
+- Mantener solo la lógica de `birth_country` (sigue siendo relevante para migración de identidad)
+- Simplificar `build_team_nation_map` y `upgrade_team_identity` para no leer football_nation
+- Actualizar el test `upgrade_game_football_identities_populates_new_fields` para usar solo `birth_country`
+
+**Verificación:** `cargo build -p ofm_core` + `cargo test -p ofm_core -- identity_upgrade`
+
+---
+
+### Paso 2: Domain Types (4 archivos) + Tests juntos
+
+Eliminar el campo `football_nation` de los tipos domain. Como los repos DB también usan estos tipos, este paso provocará errores de compilación en `db` crate — es esperado.
+
+| Archivo | Eliminar |
+|---------|----------|
+| `domain/src/player.rs` | `pub football_nation: String`, inicialización en `new()` |
+| `domain/src/team.rs` | `pub football_nation: String`, `let football_nation = normalize(...)` en `new()` |
+| `domain/src/manager.rs` | `pub football_nation: String`, `let football_nation = normalize(...)` en `new()` |
+| `domain/src/staff.rs` | `pub football_nation: String`, `football_nation: String::new()` en `new()` |
+
+**Verificación:** `cargo build -p domain` debe compilar. `cargo build -p ofm_core` también (gracias a Paso 1).
+
+---
+
+### Paso 3: DB Repositories + Tests en una sola pasada (4 archivos)
+
+Cada archivo de repositorio se modifica en UNA sola visita, incluyendo tanto el código de producción como los tests `#[cfg(test)]`.
+
+**Para cada repo, cambiar:**
+1. **INSERT**: eliminar `football_nation` de lista de columnas, re-numerar `?N` placeholders, eliminar `x.football_nation` de `params![]`
+2. **SELECT**: eliminar `football_nation` de lista de columnas, eliminar `football_nation: row.get(N)?,` del struct parser
+3. **Tests**: eliminar `x.football_nation = "..."`, `x.football_nation.clear()`, y `assert_eq!(x.football_nation, "...")`
+
+| Archivo | INSERT columns original | INSERT columns final | Notas |
+|---------|------------------------|---------------------|-------|
+| `db/src/repositories/player_repo.rs` | 34 cols | 33 cols | El más grande. Cuidado con los `?` placeholders |
+| `db/src/repositories/team_repo.rs` | ~35 cols | ~34 cols | Tiene muchas columnas JSON |
+| `db/src/repositories/manager_repo.rs` | 16 cols | 15 cols | El más simple |
+| `db/src/repositories/staff_repo.rs` | 15 cols | 14 cols | Similar a manager |
+
+**Verificación:** `cargo build -p db` + `cargo test -p db` debe pasar.
+
+---
+
+### Paso 4: Tests externos y World Export (2 archivos + 1 test de integración)
+
+Archivos con tests que referencian `football_nation` fuera de los repositorios:
+
+| Archivo | Cambios |
+|---------|---------|
+| `db/src/save_manager.rs` | `test_identity_upgrade_football_identities`: eliminar `.football_nation.clear()` y asserts |
+| `db/tests/academy_team_persistence.rs` | INSERT SQL: eliminar `football_nation` de columnas |
+| `ofm_core/src/generator/world_io.rs` | `export_world_to_json_writes_canonical_football_identity_fields`: eliminar `.clear()` y asserts |
+| `src/commands/world.rs` | `export_world_database_internal_writes_canonicalized_world_json`: eliminar `.clear()` y asserts |
+| `src/commands/world.rs` | `write_temp_database_roundtrips_football_identity_fields`: eliminar asserts |
+
+**Verificación:** `cargo test -p db -p ofm_core` debe pasar.
+
+---
+
+### Paso 5: Activar V39 (1 archivo)
+
+1. En `migrations.rs`:
+ ```rust
+ // Cambiar:
+ // V39: (reserved — remove football_nation from tables)
+ // Por:
+ M::up_with_hook("SELECT 1;", migrate_drop_football_nation),
+ ```
+2. Incrementar `MIGRATION_COUNT` de 40 a 41
+
+La función hook `migrate_drop_football_nation` ya existe en el código (agrega columnas faltantes + ejecuta `v039_drop_football_nation.sql`). **No necesita cambios.**
+
+**Verificación:**
+- `cargo build -p db` compila
+- `cargo test -p db` pasa (123 tests con V39 aplicando recreación de tablas)
+- El test `test_apply_migrations_to_empty_db` verifica que no haya `football_nation` ni `player_match_stats` legacy
+
+---
+
+### Paso 6: Cleanup (1 archivo)
+
+1. `domain/src/identity.rs`: remover `normalize_football_nation_code()` y sus tests. Si `identity_upgrade.rs` ya no lo usa y ningún otro módulo lo referencia, se puede borrar.
+2. Verificar con `cargo build --workspace` que no haya `unused function` warnings.
+
+---
+
+## Orden de commits sugerido
+
+```
+1. feat(core): simplify identity_upgrade.rs without football_nation
+2. feat(domain): remove football_nation field from Player, Team, Manager, Staff
+3. feat(db): remove football_nation from repository INSERT/SELECT and tests
+4. fix(tests): update world export and save_manager tests without football_nation
+5. feat(db): enable V39 migration to drop football_nation column
+6. chore(domain): remove unused normalize_football_nation_code
+```
+
+Se verifican 1-2 (domain compila), 1-3 (db compila), 1-4 (tests pasan), 1-5 (migración funciona), 1-6 (limpio).
+
+---
+
+## Tiempo estimado (revisado)
+
+| Paso | Archivos | Esfuerzo | Riesgo |
+|------|----------|----------|--------|
+| 1. Identity upgrade | 1 | 15 min | Bajo |
+| 2. Domain types | 4 | 15 min | Bajo |
+| 3. DB repos + tests | 4 | 45 min | **Medio** — conteo de params en player_repo |
+| 4. Tests externos | 5 | 20 min | Bajo |
+| 5. Activar V39 | 1 | 5 min | Bajo |
+| 6. Cleanup | 1 | 5 min | Bajo |
+| **Total** | **16** | **~1h 45min** | |
diff --git a/docs/proposals/FOOTBALL_REMNANTS.md b/docs/proposals/FOOTBALL_REMNANTS.md
new file mode 100644
index 000000000..559c39027
--- /dev/null
+++ b/docs/proposals/FOOTBALL_REMNANTS.md
@@ -0,0 +1,78 @@
+# Análisis de Restos de Fútbol en OLManager
+
+> Fecha: 2026-05-02
+> Rama: `feat/85-remove-football-nation` (post-removal de `football_nation`)
+
+---
+
+## ✅ YA RESUELTOS (Fase 1 + PRs recientes)
+
+| Término | Dónde | Estado |
+|---------|-------|--------|
+| `football_nation` | Domain types, repos, DB | ✅ Eliminado (V39) |
+| `Position` (enum legacy) | `domain/src/stats.rs` | ✅ Se mantiene para backward compat |
+| `goals` → `kills` | `PlayerSeasonStats` | ✅ Renombrado |
+| `draws` | `ManagerCareerStats`, `StandingEntry` | ✅ Eliminado |
+| `stadium_name/capacity` → `arena_*` | Migraciones SQL | ✅ Migrado (V35/V36) |
+| `football_identity.rs` → `identity_upgrade.rs` | Archivo | ✅ Renombrado |
+| `player_match_stats` → `lol_*` | Tablas DB | ✅ Migrado (V37/V38) |
+
+---
+
+## 🟡 PUEDEN QUEDAR (código legacy, sin impacto)
+
+| Término | Archivo | Motivo |
+|---------|---------|--------|
+| `Goalkeeper`, `Defender`, `Midfielder`, `Forward`, `Striker`, `Winger` | `domain/src/stats.rs` — `Position` enum | Legacy enum mantenido para deserializar saves viejos |
+| `goalkeeper`, `defender`, etc. | Tests en `save_manager.rs`, `player_repo.rs` | Data de test legacy — no afecta producción |
+| `penalty`, `foul`, `substitution` | `engine/src/report.rs` | Engine de simulación de partidos (general purpose) |
+
+---
+
+## 🔴 PENDIENTE DE REVISIÓN
+
+### 1. `StandingEntry.goals_for` / `goals_against` — 11 ocurrencias
+
+**Archivo:** `domain/src/league.rs`
+
+```rust
+pub struct StandingEntry {
+ pub goals_for: u32, // → renombrar a maps_won / games_won
+ pub goals_against: u32, // → renombrar a maps_lost / games_lost
+}
+```
+
+**Impacto:** Afecta `ofm_core`, `db`, frontend (types.ts).
+**Esfuerzo:** ~30 min (cambio en domain + repos + frontend).
+**Prioridad:** 🟡 Media (solo semántica, no afecta funcionalidad).
+
+### 2. `GoalDetail` en engine — 4 ocurrencias
+
+**Archivo:** `engine/src/report.rs`
+
+```rust
+pub struct GoalDetail { // → KillDetail (ya existe como concepto en LoL)
+ pub is_penalty: bool, // → eliminar o renombrar
+}
+```
+
+**Impacto:** Solo engine crate, no afecta IPC.
+**Esfuerzo:** ~15 min.
+**Prioridad:** 🟢 Baja (engine es legacy).
+
+### 3. Engine soccer terms — ~80 ocurrencias
+
+Términos como `Penalty`, `FreeKick`, `Offside`, `Substitution`, `Foul` en el engine crate.
+
+**Impacto:** Solo engine crate — NO afecta el frontend ni la DB. El engine es un crate separado que simula partidos de fútbol (herencia de OpenFootManager).
+**Prioridad:** 🔴 Ninguna — el engine no se usa para la simulación LoL (`lol_sim_v2.rs` es el motor actual).
+
+---
+
+## 📊 RESUMEN
+
+| Prioridad | Item | Esfuerzo | ¿Hacer? |
+|-----------|------|----------|---------|
+| 🟡 Media | Renombrar `goals_for`/`goals_against` → `maps_won`/`maps_lost` | 30 min | ✅ Recomendado |
+| 🟢 Baja | `GoalDetail` → `KillDetail` | 15 min | 🔲 Si hay tiempo |
+| ⚪ Ninguna | Engine soccer terms (Penalty, Foul, etc.) | — | ❌ No tocar (código legacy aislado) |
diff --git a/docs/proposals/README.md b/docs/proposals/README.md
index f436efc31..07b339d4f 100644
--- a/docs/proposals/README.md
+++ b/docs/proposals/README.md
@@ -2,9 +2,9 @@
> **Branch**: `QoL-UI`
> **Fork**: `NicoRuedaA/OLManager` → **Upstream**: `OpenLeagueManager/OLManager`
-> **Estado**: ✅ Ready for Merge
+> **Estado**: 🔄 In Progress (Champion System + Migration Fixes)
> **Fecha**: 2026-04-29
-> **Última actualización**: 2026-04-29 (Documentación actualizada post-implementación)
+> **Última actualización**: 2026-05-01 (Champion system, migration fixes, UI redesign)
> **PR**: Creado en GitHub
> **Checks**: ✅ frontend-install passed, ✅ rust-check passed
@@ -20,6 +20,10 @@ Este branch contiene mejoras de UI/UX (Quality of Life) para OLManager, enfocada
- Columna de fotos en lista de transfers (TransfersTab)
- Logo LEC en sección de torneos
- Overall (OVR) en banner de estadísticas del perfil de jugador
+- **🆕 Sistema completo de campeones** (DB, catálogo, perfil, counterpicks, sinergias)
+- **🆕 ChampionPage rediseñada** con mismo estilo visual que PlayerProfile
+- **🆕 ChampionProfile modal** con banner hero matching PlayerProfileHeroCard
+- **🆕 Fix de migraciones V31/V32** para saves viejos sin tabla champions
**🗑️ Features removidas:**
- Avatar del manager (creación de partida + settings en-game)
@@ -27,9 +31,12 @@ Este branch contiene mejoras de UI/UX (Quality of Life) para OLManager, enfocada
**Cambios técnicos:**
- Componente `RoleBadge` reutilizable
- Iconos locales (sin dependencias externas)
+- **🆕 32 migraciones de base de datos** (champions, champion_progression, avatar)
+- **🆕 Fix de bug crítico**: nombres de campeones bugueados ('Taliyah' → '. aliyah')
+- **🆕 Fix de carga de partidas**: tabla champion_progression_state inexistente en saves viejos
- Build: ✅ Exitoso
- TypeScript: ✅ Sin errores
-- 22 commits totales
+- Tests DB: ✅ 123 passing
Todas las mejoras son **no-rompientes** (backwards compatible) y siguen las convenciones del proyecto.
@@ -37,12 +44,62 @@ Todas las mejoras son **no-rompientes** (backwards compatible) y siguen las conv
## 🎯 Changes Implemented
-### 1. **Role Icons System** `feat(ui): add role icons to player lists and champion tier lists`
+### 1. **Champion System** (🆕 2026-05-01)
#### 📝 Archivos creados:
| Archivo | Tipo | Descripción |
|---------|------|-------------|
-| `src/lib/roleIcons.ts` | **NUEVO** | Helper centralizado con paths, variantes y abreviaturas de roles |
+| `src-tauri/crates/db/src/sql/v030_champions_table.sql` | **NUEVO** | Schema de tabla champions |
+| `src-tauri/crates/db/src/sql/v031_fix_champion_seed.sql` | **NUEVO** | Fix counterpicks/synergies seed |
+| `src-tauri/crates/db/src/sql/v032_fix_champion_names.sql` | **NUEVO** | Re-seed con nombres correctos |
+| `src-tauri/crates/db/src/repositories/champion_repo.rs` | **NUEVO** | CRUD + seed desde JSON |
+| `src-tauri/crates/db/src/repositories/champion_progression_repo.rs` | **NUEVO** | Persistencia de mastery + patch |
+| `src/components/champions/ChampionsTab.tsx` | **NUEVO** | Tab de catálogo de campeones |
+| `src/components/champions/ChampionCard.tsx` | **NUEVO** | Card de campeón con lazy loading |
+| `src/components/champions/ChampionProfile.tsx` | **NUEVO** | Modal de perfil (rediseñado) |
+| `src/pages/ChampionPage.tsx` | **NUEVO** | Página individual de campeón |
+| `src-tauri/src/commands/champion.rs` | **NUEVO** | Comandos Tauri para campeones |
+
+#### 📝 Archivos modificados:
+| Archivo | Cambio |
+|---------|--------|
+| `src-tauri/crates/db/src/migrations.rs` | 32 migraciones (V30-V32 champions) |
+| `src-tauri/crates/db/src/game_database.rs` | ensure_champions() idempotente |
+| `src-tauri/crates/db/src/game_persistence.rs` | Seed champions en write/read |
+| `src-tauri/crates/db/src/save_manager.rs` | Debug logging para load_game |
+| `src-tauri/src/lib.rs` | Registro de comandos champion |
+| `src/store/gameStore.ts` | Champions en GameStateData |
+| `src/pages/Dashboard.tsx` | ChampionsTab integrado |
+| `src/components/playerProfile/PlayerProfile.tsx` | onViewChampion handler |
+| `src/components/playerProfile/PlayerProfileChampionsCard.tsx` | Cards clickeables |
+| `src/components/ui/index.ts` | Exporta ChampionsTab |
+| `src/lib/roleIcons.ts` | Iconos para champion roles |
+
+#### 🎨 Características:
+- ✅ **Catálogo completo**: 170 campeones desde Data Dragon
+- ✅ **Counterpicks y sinergias**: Datos seedeados desde JSON
+- ✅ **Lazy loading**: IntersectionObserver para tiles
+- ✅ **Perfil visual**: Banner hero matching PlayerProfileHeroCard
+- ✅ **QuickStats**: Win Rate, Pick Rate, Ban Rate, KDA, Tier, Dificultad (placeholders)
+- ✅ **Responsive**: Grid adaptativo desktop/mobile
+- ✅ **Migraciones condicionales**: V31/V32 verifican tabla existe antes de DELETE
+
+#### 🐛 Bugs Fixados:
+| Bug | Causa | Fix |
+|-----|-------|-----|
+| `Taliyah` → `. aliyah` | camelCase logic reemplazaba primera mayúscula | V32 migration + fix en champion_repo.rs |
+| `no such table: champions` | Saves viejos sin tabla champions | V31/V32 con up_with_hook condicional |
+| `no such table: champion_progression_state` | load_state no verificaba tabla | Check sqlite_master antes de query |
+| Partida no cargaba | champion_progression_repo crash | Table existence check |
+
+---
+
+### 2. **Role Icons System**
+
+#### 📝 Archivos creados:
+| Archivo | Tipo | Descripción |
+|---------|------|-------------|
+| `src/lib/roleIcons.ts` | **NUEVO** | Helper centralizado con paths, variantes y abreviaturas |
| `src/components/ui/RoleBadge.tsx` | **NUEVO** | Componente reutilizable Badge + Icono |
| `public/role-icons/*.png` | **NUEVO** | 6 iconos: top.png, jungler.png, mid.png, adc.png, support.png, allroles.png |
@@ -57,313 +114,94 @@ Todas las mejoras son **no-rompientes** (backwards compatible) y siguen las conv
| `src/components/champions/ChampionsTab.tsx` | Cambia de URLs externas (CommunityDragon) a iconos locales |
| `src/components/playerProfile/PlayerProfileHeroCard.tsx` | Reemplaza Badge con RoleBadge |
-#### 🎨 Características:
-- ✅ **Componente reutilizable**: ``
-- ✅ **Iconos locales**: Sin dependencias externas, carga más rápida
-- ✅ **DRY**: Elimina definiciones duplicadas de `roleBadgeVariant`
-- ✅ **Consistencia visual**: Mismo estilo en todas las listas y filtros
-- ✅ **Fácil mantenimiento**: Single source of truth en `src/lib/roleIcons.ts`
-- ✅ **Contornos de color**: Cada role tiene contorno del color correspondiente
-- ✅ **Opciones**:
- - `size`: "sm" | "md" | "lg"
- - `showLabel`: muestra abreviatura (ej: "JG", "SUP")
- - `className`: custom classes
- - `title`: tooltip personalizado
-
#### 🎯 Roles y colores:
-| Role | Color | Abreviatura | Icono en |
-|------|-------|-------------|----------|
-| TOP | danger (rojo) | TOP | Listas + Filtros |
-| JUNGLE | success (verde) | JG | Listas + Filtros |
-| MID | accent (amarillo) | MID | Listas + Filtros |
-| ADC | primary (azul) | ADC | Listas + Filtros |
-| SUPPORT | neutral (gris) | SUP | Listas + Filtros |
-| ALL | white/silver | - | Filtro "Todos" |
-
-#### 🔁 Filtros de roles actualizados:
-**Antes** (texto):
-```
-[Todos] [TOP] [JG] [MID] [ADC] [SUP]
-```
-
-**Después** (iconos):
-```
-[⚪] [🔴] [🟢] [🟡] [🔵] [⚪]
-```
-
-- ✅ **Tooltip**: Hover sobre el icono muestra el nombre completo del role
-- ✅ **Mismo comportamiento**: Click para filtrar, activo/inactivo con colores
-- ✅ **Consistencia**: Mismos iconos en listas y filtros
+| Role | Color | Abreviatura |
+|------|-------|-------------|
+| TOP | danger (rojo) | TOP |
+| JUNGLE | success (verde) | JG |
+| MID | accent (amarillo) | MID |
+| ADC | primary (azul) | ADC |
+| SUPPORT | neutral (gris) | SUP |
---
-### 2. **Player Photos in Players List** `feat(ui): add player photos column to players list`
-
-#### 📝 Archivos modificados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `src/components/players/PlayersListTab.tsx` | Modificado | Agregada columna de foto con `resolvePlayerPhoto()` |
-
-#### 🎨 Características:
-- ✅ **Columna de foto**: Primera columna en la tabla de jugadores
-- ✅ **Fallback**: Usa foto por defecto si no hay foto personalizada
-- ✅ **Error handling**: `onError` fallback a foto genérica
-- ✅ **Lazy loading**: Carga bajo demanda para performance
-
----
+### 3. **Player Photos in Lists**
-### 3. **Player Photos in Transfers List** `feat(ui): add player photos column to transfers list`
-
-#### 📝 Archivos modificados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `src/components/transfers/TransfersTab.tsx` | Modificado | Agregada columna de foto con `resolvePlayerPhoto()` |
-
-#### 🎨 Características:
-- ✅ **Columna de foto**: Primera columna en la tabla de transfers
-- ✅ **Fallback**: Usa foto por defecto si no hay foto personalizada
-- ✅ **Error handling**: `onError` fallback a foto genérica
-- ✅ **Consistencia**: Misma lógica que PlayersList
+| Archivo | Cambio |
+|---------|--------|
+| `src/components/players/PlayersListTab.tsx` | Columna de foto con `resolvePlayerPhoto()` |
+| `src/components/transfers/TransfersTab.tsx` | Columna de foto con `resolvePlayerPhoto()` |
---
-### 4. **LEC Logo in Tournaments** `feat(ui): add LEC logo to tournaments section`
+### 4. **LEC Logo in Tournaments**
-#### 📝 Archivos creados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `public/lec-logo.png` | **NUEVO** | Logo oficial de LEC (7.1 KB) |
-
-#### 📝 Archivos modificados:
| Archivo | Cambio |
|---------|--------|
+| `public/lec-logo.png` | Logo oficial de LEC (7.1 KB) |
| `src/components/tournaments/TournamentsTab.tsx` | Reemplaza ícono Trophy por logo LEC |
-#### 🎨 Características:
-- ✅ **Logo en header**: Sección de torneos ahora muestra logo LEC
-- ✅ **Contenedor blanco**: Mejor visibilidad con fondo blanco/90
-- ✅ **Consistencia**: Mismo logo para Winter/Spring/Summer splits
-
---
-### 5. **OVR in Player Profile** `feat(ui): add OVR label to player profile stats banner`
+### 5. **OVR in Player Profile**
-#### 📝 Archivos modificados:
| Archivo | Cambio |
|---------|--------|
| `src/components/playerProfile/PlayerProfileHeroCard.tsx` | Agregado OVR al banner de estadísticas |
-#### 🎨 Características:
-- ✅ **Layout 3x2**: OVR | Energía | Moral / Potencial | Valor | Salario
-- ✅ **OVR destacado**: Color accent (cyan) para énfasis
-- ✅ **Responsive**: Mismo layout en desktop y mobile
-- ✅ **Traducción**: Usa `t("common.ovr")` para i18n
-
---
-### 6. **Manager Avatar Removal** `feat(ui): remove manager avatar feature`
+### 6. **Manager Avatar Removal**
-#### 📝 Archivos modificados:
| Archivo | Cambio |
|---------|--------|
| `src/pages/MainMenu.tsx` | Eliminada sección de avatar upload (~143 líneas) |
| `src/components/manager/ManagerTab.tsx` | Eliminada sección de avatar upload (~120 líneas) |
-#### 🗑️ Cambios:
-- ✅ **Creación de partida**: Removida opción de foto de perfil
-- ✅ **Settings en-game**: Removida opción de foto de perfil
-- ✅ **Profile card**: Ahora muestra iniciales del manager (ej: "JM")
-- ✅ **Limpieza**: Eliminados imports de `managerAvatars` library
-- ✅ **Simplificación**: Formulario más directo (sin validación de imágenes)
-
-#### 📊 Impacto:
-- **Líneas eliminadas:** ~263
-- **Estado eliminado:** `avatarFile`, `avatarPreview`, `avatarError`, `fileInputRef`
-- **Handlers eliminados:** `handleAvatarChange`, `handleRemoveAvatar`
-- **Backend:** `avatarPath: null` en `start_new_game` y `update_manager_profile`
-
---
## 🛠️ Technical Details
-- ✅ **Fallback**: Si no hay avatar o falla la carga, muestra SVG por defecto
-- ✅ **Modern Base64**: Usa `base64::engine::general_purpose::STANDARD.encode()` (no deprecated)
-
----
-
-### 2. **Manager Settings Modal** `feat(ui): add settings button to edit manager profile`
-
-#### 📝 Archivos modificados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `src/components/manager/ManagerTab.tsx` | Modificado | Botón ⚙️ (gear icon) + modal para editar perfil |
-| `src-tauri/src/commands/game.rs` | Modificado | Comando `update_manager_profile` |
-| `src-tauri/src/lib.rs` | Modificado | Registro de `update_manager_profile` |
-
-#### 🎨 Características:
-- ✅ **Botón Settings**: Esquina superior derecha de la card de perfil (ícono de engranaje)
-- ✅ **Modal**: Usa el patrón existente `DashboardModalFrame` (consistente con el resto del proyecto)
-- ✅ **Campos editables**:
- - Nickname
- - First name / Last name
- - Date of birth (input type="date")
- - Nationality (dropdown con `allNationalities` de `countries.ts`)
- - Avatar (misma lógica que en creación de partida)
-- ✅ **Actualización inmediata**: Después de guardar, el store local se actualiza automáticamente
-- ✅ **Backend**: Solo actualiza los campos proveídos (no `None`), persiste en el game state
-
----
-
-### 3. **Schedule Fixture Alignment** `fix(ui): align VS/score column in schedule fixture list`
-
-#### 📝 Archivos modificados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `src/components/schedule/ScheduleTab.tsx` | Modificado | Cambio de layout de 3 columnas a 5 columnas |
-
-#### 🎨 Antes vs Después:
-
-**Antes** (alineación incorrecta):
-```
-BO1 Fnatic VS G2 Esports →
-BO1 SK Gaming VS Karmine Corp →
-BO1 Team BDS VS Team Vitality →
-```
-
-**Después** (alineación perfecta):
-```
-BO1 Fnatic | VS | G2 Esports | →
-BO1 SK Gaming | VS | Karmine Corp | →
-BO1 Team BDS | VS | Team Vitality | →
-```
-
-#### 📐 Nuevo Grid Layout:
-| Columna | Ancho | Alineación | Contenido |
-|---------|-------|------------|-----------|
-| 1 | `54px` | Left | BO badge |
-| 2 | `1fr` | **Right** | Home team + logo |
-| 3 | `60px` | **Center** | VS o Score |
-| 4 | `1fr` | **Left** | Away team + logo |
-| 5 | `32px` | Right | View result button |
-
----
-### 4. **Player Photos in Transfers List** `feat(ui): add player photos column to transfers list`
+### Database Migrations (V1-V32)
-#### 📝 Archivos modificados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `src/components/transfers/TransfersTab.tsx` | Modificado | Agregada columna de foto con `resolvePlayerPhoto()` |
-
-#### 🎨 Características:
-- ✅ **Columna de foto**: Primera columna en la tabla de transfers
-- ✅ **Fallback**: Usa foto por defecto si no hay foto personalizada
-- ✅ **Error handling**: `onError` fallback a foto genérica
-- ✅ **Consistencia**: Misma lógica que PlayersList
-
----
-
-### 5. **Role Icons System** `feat(ui): add role icons to player lists and champion tier lists`
-
-#### 📝 Archivos creados:
-| Archivo | Tipo | Descripción |
-|---------|------|-------------|
-| `src/lib/roleIcons.ts` | **NUEVO** | Helper centralizado con paths, variantes y abreviaturas |
-| `src/components/ui/RoleBadge.tsx` | **NUEVO** | Componente reutilizable Badge + Icono |
-| `public/role-icons/*.png` | **NUEVO** | 5 iconos: top.png, jungler.png, mid.png, adc.png, support.png |
-
-#### 📝 Archivos modificados:
-| Archivo | Cambio |
-|---------|--------|
-| `src/components/ui/index.ts` | Exporta `RoleBadge` |
-| `src/components/players/PlayersListTab.tsx` | Reemplaza Badge con RoleBadge + filtros con iconos |
-| `src/components/transfers/TransfersTab.tsx` | Reemplaza Badge con RoleBadge + filtros con iconos |
-| `src/components/finances/FinancesTab.tsx` | Reemplaza Badge con RoleBadge, elimina `roleBadgeVariant` duplicado |
-| `src/components/teamProfile/TeamProfileRosterCard.tsx` | Reemplaza Badge con RoleBadge, elimina `roleBadgeVariant` duplicado |
-| `src/components/champions/ChampionsTab.tsx` | Cambia de URLs externas (CommunityDragon) a iconos locales |
-
-#### 🎨 Características:
-- ✅ **Componente reutilizable**: ``
-- ✅ **Iconos locales**: Sin dependencias externas, carga más rápida
-- ✅ **DRY**: Elimina 6 definiciones duplicadas de `roleBadgeVariant`
-- ✅ **Consistencia visual**: Mismo estilo en todas las listas y filtros
-- ✅ **Fácil mantenimiento**: Single source of truth en `src/lib/roleIcons.ts`
-- ✅ **Opciones**:
- - `size`: "sm" | "md" | "lg"
- - `showLabel`: muestra abreviatura (ej: "JG", "SUP")
- - `className`: custom classes
- - `title`: tooltip personalizado
-
-#### 🎯 Roles y colores:
-| Role | Color | Abreviatura | Icono en |
-|------|-------|-------------|----------|
-| TOP | danger (rojo) | TOP | Listas + Filtros |
-| JUNGLE | success (verde) | JG | Listas + Filtros |
-| MID | accent (amarillo) | MID | Listas + Filtros |
-| ADC | primary (azul) | ADC | Listas + Filtros |
-| SUPPORT | neutral (gris) | SUP | Listas + Filtros |
-
-#### 🔁 Filtros de roles actualizados:
-**Antes** (texto):
-```
-[Todos] [TOP] [JG] [MID] [ADC] [SUP]
-```
-
-**Después** (iconos):
-```
-[Todos] [🔴] [🟢] [🟡] [🔵] [⚪]
-```
-
-- ✅ **Tooltip**: Hover sobre el icono muestra el nombre completo del role
-- ✅ **Mismo comportamiento**: Click para filtrar, activo/inactivo con colores
-- ✅ **Consistencia**: Mismos iconos en listas y filtros
-
----
-
-## 🛠️ Technical Details
+| Migración | Descripción |
+|-----------|-------------|
+| V28 | avatar_path en managers |
+| V28 (champion_progression) | Champion mastery + patch persistence |
+| V30 | Champions table (catalog) |
+| V31 | Fix counterpicks/synergies seed (DELETE condicional) |
+| V32 | Fix champion names camelCase bug (DELETE condicional) |
### Backend Commands Added
-#### `save_manager_avatar`
+#### `get_champions`
```rust
#[tauri::command]
-pub async fn save_manager_avatar(
- app_handle: tauri::AppHandle,
- filename: String,
- data: Vec,
-) -> Result
+pub async fn get_champions(state: State<'_, SaveManagerState>) -> Result, String>
```
-- **Qué hace**: Guarda el archivo en `AppData/Roaming/com.openleaguemanager.olmanager/manager-avatars/`
-- **Formato**: Nombre único generado (`manager-{timestamp}-{random}.{ext}`)
-- **Retorno**: El filename guardado
+- Retorna todos los campeones del save activo
-#### `load_manager_avatar`
+#### `get_champion_by_id`
```rust
#[tauri::command]
-pub async fn load_manager_avatar(
- app_handle: tauri::AppHandle,
- filename: String,
-) -> Result
+pub async fn get_champion_by_id(state: State<'_, SaveManagerState>, id: i64) -> Result
```
-- **Qué hace**: Lee el archivo y lo convierte a data URL (base64)
-- **Uso**: Evita problemas de rutas entre frontend/backend
-- **MIME**: Detecta automáticamente (PNG/JPG/WebP/SVG)
+- Retorna un campeón por ID
+
+### Champion Seed Flow
-#### `update_manager_profile`
-```rust
-#[tauri::command]
-pub async fn update_manager_profile(
- state: State<'_, StateManager>,
- nickname: Option,
- first_name: Option,
- last_name: Option,
- dob: Option,
- nationality: Option,
- avatar_path: Option,
-) -> Result<(), String>
```
-- **Qué hace**: Actualiza solo los campos proveídos (no `None`)
-- **Validación**: Formato de fecha, longitud de strings
-- **Persistencia**: Guarda en el game state automáticamente
+data/lec/draft/champions.json (16,353 líneas, 165 campeones)
+ ↓
+champion_repo::seed_from_json(conn, json_content)
+ ↓
+DB: champions table (id, name, champion_key, roles_json, counterpicks_json, synergies_json, image_tile_url, image_splash_url)
+```
+
+**When It Runs:**
+1. **New Game**: `GamePersistenceWriter::write_game()` → seed_from_json()
+2. **Load Game**: `GamePersistenceReader::read_game()` → db.ensure_champions() → seed si tabla vacía
+3. **Legacy Saves**: ensure_champions() crea tabla + seed si no existe
---
@@ -372,45 +210,25 @@ pub async fn update_manager_profile(
### ✅ Verificado:
#### Build & Compilation:
-- ✅ `npm run build` passes (frontend compila sin errores en ~800ms)
+- ✅ `npm run build` passes
- ✅ TypeScript: 0 errors
- ✅ `npm run tauri dev` runs without errors
-
-#### Role Icons:
-- ✅ **PlayersList** - Iconos de roles visibles en columna "Pos"
-- ✅ **PlayersList** - Filtros con iconos en lugar de texto
-- ✅ **TransfersTab** - Iconos de roles visibles
-- ✅ **TransfersTab** - Filtros con iconos en lugar de texto
-- ✅ **FinancesTab** - Iconos de roles en squad finances
-- ✅ **TeamProfileRosterCard** - Iconos de roles en roster
-- ✅ **ChampionsTab** - Iconos en filtros de tier list
-- ✅ **PlayerProfileHeroCard** - RoleBadge en perfil de jugador
-- ✅ **Contornos** - Todos los iconos tienen contorno de color
-- ✅ **Tooltip** - Hover muestra nombre completo del role
-
-#### Player Photos:
-- ✅ **PlayersList** - Columna de fotos visible
-- ✅ **TransfersTab** - Columna de fotos visible
-- ✅ **Fallback** - Foto genérica cuando no hay foto personalizada
-- ✅ **Error handling** - No rompe si falla carga de imagen
-
-#### LEC Branding:
-- ✅ **TournamentsTab** - Logo LEC visible en header
-- ✅ **Contenedor blanco** - Mejor visibilidad
-
-#### Player Profile OVR:
-- ✅ **PlayerProfileHeroCard** - OVR en banner de estadísticas
-- ✅ **Layout 3x2** - OVR | Energía | Moral / Potencial | Valor | Salario
-- ✅ **Responsive** - Mismo layout en desktop y mobile
-
-#### Manager Avatar Removal:
-- ✅ **MainMenu** - Sin sección de avatar en creación de partida
-- ✅ **ManagerTab** - Sin upload de avatar en settings
-- ✅ **Profile card** - Muestra iniciales (ej: "JM") en lugar de foto
-- ✅ **Formulario** - Más directo (sin validación de imágenes)
-
-#### i18n:
-- ✅ **Free agent** - "Agente Libre" se muestra correctamente (no `players.freeAgent`)
+- ✅ `cargo test -p db`: 123 passing
+
+#### Champion System:
+- ✅ ChampionsTab carga 170 campeones
+- ✅ ChampionCard con lazy loading
+- ✅ ChampionProfile modal con hero banner
+- ✅ ChampionPage con layout matching PlayerProfile
+- ✅ Counterpicks y sinergias visibles
+- ✅ Migraciones V30-V32 aplican correctamente
+- ✅ Saves viejos cargan sin error (tablas condicionales)
+- ✅ Nombres de campeones correctos (Taliyah = Taliyah)
+
+#### Load Game Flow:
+- ✅ Debug logging confirma pipeline completo
+- ✅ ensure_champions → meta → manager → teams(38) → players(323) → staff → messages → news → league → objectives → scouting → champion_progression
+- ✅ DONE - game loaded successfully
### ⚠️ Warnings (no críticos, código legacy):
- `unused_mut` en `live_match_manager.rs:136`
@@ -418,259 +236,16 @@ pub async fn update_manager_profile(
- `unused_import` en `game.rs:10`
- `dead_code` en `lol_sim_v2.rs`
-Estos warnings son del código original, **no de nuestros cambios**.
-
-### 🎯 Manual Testing Checklist:
-
-```markdown
-## Testing Manual
-
-### Role Icons
-- [ ] Ir a Players → Ver iconos en columna "Pos"
-- [ ] Ir a Players → Click en filtros (iconos, no texto)
-- [ ] Ir a Transfers → Ver iconos en columna "Pos"
-- [ ] Ir a Transfers → Click en filtros (iconos, no texto)
-- [ ] Ir a Finances → Ver iconos en squad finances
-- [ ] Ir a Teams → Seleccionar equipo → Ver iconos en roster
-- [ ] Ir a Champions → Ver iconos en filtros de tier list
-- [ ] Ir a Player Profile → Ver RoleBadge debajo del nombre
-- [ ] Hover sobre iconos → Ver tooltip con nombre completo
-
-### Player Photos
-- [ ] Ir a Players → Ver columna de fotos (primera columna)
-- [ ] Ir a Transfers → Ver columna de fotos (primera columna)
-- [ ] Verificar fallback (foto genérica si no hay custom)
-
-### LEC Branding
-- [ ] Ir a Tournaments → Ver logo LEC en header (reemplaza trophy)
-
-### Player Profile OVR
-- [ ] Ir a Player Profile → Ver banner con 6 estadísticas
-- [ ] Verificar layout: OVR | Cond | Moral / Potencial | Valor | Salario
-- [ ] Verificar OVR en color accent (cyan)
-
-### Manager Avatar Removal
-- [ ] Ir a Main Menu → New Game → Ver formulario (SIN avatar)
-- [ ] Crear partida → Ir a Manager → Ver iniciales (SIN foto)
-- [ ] Click en Settings → Ver campos (SIN upload de imagen)
-```
-
---
-## 📂 Documentation Updates
-
-```
-64002b4 feat(ui): remove manager avatar from new game creation
-6f7a9ba feat(ui): remove manager avatar feature
-c28b0fa feat(ui): add OVR label to player profile stats banner
-4b68229 fix(ui): resolve TypeScript errors in PlayersListTab and TransfersTab
-2ebe5e1 fix(i18n): use correct translation key for free agent
-35d3547 feat(ui): use RoleBadge in player profile hero card
-fa179d5 feat(ui): add LEC logo to tournaments section
-7d59a66 feat(ui): add white outline to allroles.png icon
-cc534f0 chore: remove temporary image processing scripts
-e962820 feat(ui): add colored outlines to role icons
-cbf5673 feat(ui): add allroles.png icon for filter buttons
-2a5fdfd feat(ui): replace 'All roles' text with icon in filters
-ec532be fix(ui): resolve all TypeScript errors and clean imports
-efe6272 fix(ui): add React import to RoleBadge and improve error handling
-64e6436 fix(ui): resolve RoleBadge import and dependency issues
-4feba8a fix(ui): make RoleBadge independent component
-7b70bae fix(ui): correct roleIcons import path
-39731c0 fix(ui): correct Badge import path in RoleBadge
-9f8ffbf docs: update PR with role filter icons changes
-754f36a feat(ui): replace role filter text with icon badges
-f163e76 docs: add role icons documentation to QoL-UI PR
-eaae106 feat(ui): add role icons to player lists and champion tier lists
-ae9eb94 feat(ui): add player photos column to transfers list
-c14d264 feat(ui): add player photos column to players list
-87c1ecf fix(ui): refresh avatar on game load by watching full manager object
-50b3093 fix(persist): save and load avatar_path from database
-9033660 docs: add comprehensive PR documentation for QoL-UI branch
-```
-
-### 📋 Commits explicados:
-1. **`feat(ui): add player photos column to players list`** - Columna de fotos en PlayersList
-2. **`feat(ui): add player photos column to transfers list`** - Columna de fotos en TransfersTab
-3. **`feat(ui): add role icons to player lists...`** - Sistema de iconos de roles completo
-4. **`docs: add role icons documentation...`** - Documentación inicial del PR
-5. **`feat(ui): replace role filter text with icon badges`** - Filtros con iconos en Players/Transfers
-6. **`docs: update PR with role filter icons changes`** - Docs actualizadas con filtros
-7. **`fix(ui): correct Badge import path in RoleBadge`** - Fix import path (Badge)
-8. **`fix(ui): correct roleIcons import path`** - Fix import path (roleIcons)
-9. **`fix(ui): make RoleBadge independent component`** - RoleBadge sin dependencia de Badge
-10. **`fix(ui): resolve RoleBadge import and dependency issues`** - Fixes de imports
-11. **`fix(ui): add React import to RoleBadge...`** - Fix React import + error handling
-12. **`fix(ui): resolve all TypeScript errors...`** - Fixes finales de TypeScript
-13. **`feat(ui): add allroles.png icon for filter buttons`** - Icono "all roles" con contorno
-14. **`chore: remove temporary image processing scripts`** - Limpieza de scripts temporales
-15. **`feat(ui): add colored outlines to role icons`** - Contornos de colores por role
-16. **`feat(ui): add white outline to allroles.png icon`** - Contorno blanco para allroles
-17. **`feat(ui): add LEC logo to tournaments section`** - Logo LEC en torneos
-18. **`feat(ui): use RoleBadge in player profile hero card`** - RoleBadge en perfil de jugador
-19. **`fix(i18n): use correct translation key for free agent`** - Fix traducción "Agente Libre"
-20. **`fix(ui): resolve TypeScript errors in PlayersListTab...`** - Fix TypeScript (team_id null)
-21. **`feat(ui): add OVR label to player profile stats banner`** - OVR en banner de jugador
-22. **`feat(ui): remove manager avatar feature`** - Eliminado avatar de ManagerTab (in-game)
-23. **`feat(ui): remove manager avatar from new game creation`** - Eliminado avatar de MainMenu
-
-### 📊 Stats finales:
-- **Total commits:** 23
-- **Líneas agregadas:** ~500 (role icons, player photos, LEC logo, OVR)
-- **Líneas eliminadas:** ~263 (manager avatar removal)
-- **Archivos creados:** 8 (roleIcons.ts, RoleBadge.tsx, 6 iconos PNG)
-- **Archivos modificados:** 15+
+## 📊 Stats finales:
+- **Total commits:** 40+
+- **Migraciones:** 32
+- **Archivos creados:** 15+
+- **Archivos modificados:** 25+
- **Build time:** ~800ms
- **TypeScript errors:** 0
-
-```
-754f36a feat(ui): replace role filter text with icon badges
-f163e76 docs: add role icons documentation to QoL-UI PR
-eaae106 feat(ui): add role icons to player lists and champion tier lists
-ae9eb94 feat(ui): add player photos column to transfers list
-c14d264 feat(ui): add player photos column to players list
-87c1ecf fix(ui): refresh avatar on game load by watching full manager object
-50b3093 fix(persist): save and load avatar_path from database
-9033660 docs: add comprehensive PR documentation for QoL-UI branch
-47a7a77 fix(ui): align VS/score column in schedule fixture list
-1a5147d fix: correct nickname type mismatch in update_manager_profile
-8497a6e feat(ui): add settings button to edit manager profile
-500d4a7 docs: update migration plan and reorganize proposal docs
-76edb78 docs(roadmap): clarify identity migration with nationality_code + competitive_region
-0df94be docs: add roadmap and data migration plan
-652b321 feat(ui): add manager avatar upload and display
-1d01f36 Changed wring version (upstream/main)
-```
-
-### 📋 Commits explicados:
-1. **`feat(ui): add manager avatar upload and display`** - Feature completa de avatar
-2. **`docs: add roadmap and data migration plan`** - Documentación recuperada del session anterior
-3. **`docs(roadmap): clarify identity migration...`** - Corrección de conceptos LoL
-4. **`docs: update migration plan and reorganize...`** - Reorganización a `docs/proposals/`
-5. **`feat(ui): add settings button...`** - Modal de edición de perfil
-6. **`fix: correct nickname type mismatch...`** - Bug fix (String vs Option)
-7. **`fix(ui): align VS/score column...`** - Alineación de calendario
-8. **`fix(persist): save and load avatar_path...`** - Fix: avatar se pierde al recargar partida
-9. **`fix(ui): refresh avatar on game load...`** - Fix: useEffect no detectaba cambios en gameState
-10. **`feat(ui): add player photos column to players list`** - Columna de fotos en lista de jugadores
-11. **`feat(ui): add player photos column to transfers list`** - Columna de fotos en transfers
-12. **`feat(ui): add role icons to player lists...`** - Iconos de roles en badges de listas y champions
-13. **`docs: add role icons documentation...`** - Documentación completa del sistema de iconos
-14. **`feat(ui): replace role filter text with icon badges`** - Botones de filtro ahora usan iconos en vez de texto
-
----
-
-## 🔍 How to Test (Para el reviewer)
-
-### Pre-requisitos:
-```bash
-# Instalar dependencias
-npm install
-
-# Instalar Rust (si no lo tenés)
-# https://rustup.rs/
-
-# Ejecutar en modo desarrollo
-$env:Path += ";$env:USERPROFILE\.cargo\bin"
-npm run tauri dev
-```
-
-### Pasos de prueba:
-
-#### 1. **Role Icons**:
-1. Ir a pestaña **"Players"** → ✅ Ver iconos de roles (TOP, JG, MID, ADC, SUP) con colores
-2. Ir a pestaña **"Transfers"** → ✅ Mismos iconos de roles
-3. Ir a pestaña **"Finances"** → ✅ Mismos iconos de roles
-4. Ir a pestaña **"Champions"** → ✅ Iconos de roles en los filtros (arriba del tier list)
-5. Ir a pestaña **"Teams"** → Seleccionar un equipo → ✅ Ver iconos de roles en el roster
-6. ✅ Verificar colores: TOP (rojo), JUNGLE (verde), MID (amarillo), ADC (azul), SUPPORT (gris)
-7. ✅ Hover sobre iconos → Tooltip muestra nombre completo
-
-#### 2. **Player Photos**:
-1. Ir a pestaña **"Players"**
-2. ✅ Ver columna de fotos en la primera columna
-3. Ir a pestaña **"Transfers"**
-4. ✅ Ver columna de fotos en la primera columna
-5. ✅ Las fotos se ven correctamente (sin errores de carga)
-
-#### 3. **LEC Logo**:
-1. Ir a pestaña **"Tournaments"**
-2. ✅ Ver logo LEC en header (reemplaza ícono de trophy)
-3. ✅ Contenedor blanco con mejor visibilidad
-
-#### 4. **Player Profile OVR**:
-1. Ir a pestaña **"Players"**
-2. Click en cualquier jugador
-3. ✅ Ver banner de estadísticas con layout 3x2
-4. ✅ OVR en color accent (cyan), arriba a la izquierda
-5. ✅ Layout: OVR | Energía | Moral / Potencial | Valor | Salario
-
-#### 5. **Manager Avatar Removal**:
-1. Ir a **Main Menu** → Click en **"New Game"**
-2. ✅ Ver formulario de creación (SIN sección de avatar)
-3. ✅ Campos: Nick, Nombre, Apellido, Fecha, Nacionalidad → Start
-4. Crear partida → Ir a pestaña **"Manager"**
-5. ✅ Ver iniciales del manager (ej: "JM") en lugar de foto
-6. Click en **⚙️ Settings**
-7. ✅ Ver campos de edición (SIN upload de imagen)
-
-#### 6. **Schedule Alignment** (existente):
-1. Ir a pestaña **"Calendar"** (o "Schedule")
-2. ✅ Verificar que todos los **"VS"** y **scores** están alineados verticalmente
-3. ✅ Home teams a la derecha, Away teams a la izquierda
-
----
-
-## 📊 Directory Structure (Para el reviewer)
-
-```
-docs/
-├── ARCHITECTURE.md ← Existente (upstream)
-├── GOVERNANCE.md ← Existente (upstream)
-├── DATA_PROVENANCE.md ← Existente (upstream)
-├── INHERITED_DOCS_AUDIT.md ← Existente (upstream)
-├── RELEASE_PROCESS.md ← Existente (upstream)
-├── legacy/ ← Existente (upstream)
-└── proposals/ ← 🆕 NUEVA carpeta para este PR
- ├── ROADMAP.md ← Roadmap del proyecto
- ├── DATA_MIGRATION_PLAN.md ← Plan de migración (actualizado)
- └── MANAGER_AVATAR_FEATURE.md ← Documentación de la feature
-```
-
----
-
-## 🎯 PR Checklist (Para el reviewer)
-
-- [x] Código sigue las convenciones del proyecto
-- [x] Commits siguen [Conventional Commits](https://www.conventionalcommits.org/)
-- [x] Backwards compatible (no rompe saves existentes)
-- [x] Documentación actualizada
-- [x] Build passes (`npm run build`)
-- [x] Rust compiles (`cargo build --workspace`)
-- [x] No hay errores de runtime en consola
-- [x] UI/UX mejorada siguiendo patrones existentes
-- [x] Archivos organizados en `docs/proposals/` para fácil revisión
-
----
-
-## 💬 Notas para el Maintainer
-
-1. **¿Por qué `nationality_code` + `competitive_region` y no solo `region`?**
- - En LoL, "región" (LCK, LEC, LCS) y "nacionalidad" (KR, ES, FR) son conceptos diferentes
- - Un jugador coreano puede competir en la LEC europea
- - Separar ambos conceptos permite representar correctamente la realidad del esport
-
-2. **Base64 API Moderna**:
- - Migré de `base64::encode()` (deprecated en 0.21) a `base64::engine::general_purpose::STANDARD.encode()` (0.22)
- - Esto elimina warnings de deprecación
-
-3. **Organización de docs**:
- - Moví todo a `docs/proposals/` para que el reviewer tenga todo centralizado
- - El roadmap y plan de migración son **propuestas** para el futuro del proyecto
-
-4. **Backwards Compatibility**:
- - `avatar_path` es `Option` (nullable) → saves sin avatar siguen funcionando
- - `update_manager_profile` solo actualiza campos proveídos → no rompe nada
+- **DB tests:** 123 passing
---
@@ -678,9 +253,7 @@ docs/
- **Fork**: [NicoRuedaA/OLManager](https://github.com/NicoRuedaA/OLManager)
- **Branch**: [`QoL-UI`](https://github.com/NicoRuedaA/OLManager/tree/QoL-UI)
-- **Compare**: [upstream/main...QoL-UI](https://github.com/NicoRuedaA/OLManager/compare/QoL-UI)
-- **Open PR**: [Create Pull Request](https://github.com/NicoRuedaA/OLManager/pull/new/QoL-UI)
---
-*Última actualización: 2026-04-29 11:45 AM*
+*Última actualización: 2026-05-01*
diff --git a/docs/proposals/ROADMAP.md b/docs/proposals/ROADMAP.md
index 575dbed4f..ba1d469d8 100644
--- a/docs/proposals/ROADMAP.md
+++ b/docs/proposals/ROADMAP.md
@@ -17,118 +17,214 @@ OLManager es un manager de esports para League of Legends diseñado para simular
| Métrica | Valor |
|--------|-------|
-| **Versión** | 0.1.1 (pre-alpha) |
+| **Versión** | 0.1.2 (pre-alpha) |
+| **Análisis técnico** | `docs/proposals/analisis.md` — 44 hallazgos documentados |
| **Stack** | React 19 + TypeScript 6.0 + Vite 8 + TailwindCSS 4 + Tauri v2 (Rust) |
-| **DB** | SQLite (27 migraciones) |
-| **Test Files** | 106 frontend + 21 backend Rust |
+| **LOC Frontend** | ~71.500 TS/TSX, 228 componentes |
+| **LOC Backend** | ~77.000 Rust, 173 archivos, 4 crates |
+| **DB** | SQLite per-save (37 migraciones versionadas) |
+| **Tests** | 107 frontend (Vitest) + 125 Rust tests (5 legacy rotos) |
| **i18n** | 7 idiomas configurados |
| **Commits** | Conventional commits |
-| **PR Activo** | `QoL-UI` - 23 commits, ready for merge |
-### Features Recientes (QoL-UI Branch)
-
-✅ **Implementadas:**
-- Role icons system (TOP, JUNGLE, MID, ADC, SUPPORT)
-- Player photos en PlayersList y TransfersTab
-- LEC logo en torneos
-- OVR en perfil de jugador
-- Manager avatar removido (simplificación)
-
-### Deuda Técnica Identificada
-
-- ⚠️ Herencia de nombres/estructuras del proyecto original de fútbol
-- ⚠️ Documentación legacy en `docs/legacy/inherited-docs/`
-- ⚠️ 2 TODOs pendientes en `lol_sim_v2.rs` (sistema de movimiento)
-- ⚠️ Tests de Rust marcados como "experimental" en CI
+### ✅ Fase 1 Completada (2026-05-02)
+
+La Fase 1 de hardening y foundation está completa. Ver `docs/proposals/analisis.md` para el análisis técnico original.
+
+| Issue resuelto | PR(split) | Estado |
+|---------------|-----------|--------|
+| Security hardening (path traversal, CSP, capabilities) | #121 (#125) | ✅ |
+| StateManager unification (4 Mutex → 1 Session) | #121 (#125) | ✅ |
+| Break god files (avatar.rs extraído a game_setup/) | #121 (#125) | ✅ |
+| CI/CD audit gates (cargo audit, npm audit, tests blocking) | #121 (#125) | ✅ |
+| Legacy tests (123 db tests pass, legacy marcados) | #121 (#125) | ✅ |
+| Input validation (validator + Zod) | #121 (#125) | ✅ |
+| AppError enum (thiserror + códigos) | #121 (#125) | ✅ |
+| Architecture docs (ADRs + Mermaid C4) | #121 (#125) | ✅ |
+| Cross-stack types (ts-rs derives en 100+ tipos) | #121 (#125) | ✅ |
+| Champions catalog (#64) | #121 (#125) | ✅ |
+| Database defutbolization + domain cleanup (#85) | #122 (#126) | ✅ |
+| Schema cleanup migrations V37-V42 (#106) | #122 (#126) | ✅ |
+| Remove football fields from PlayerSeasonStats (#114) | #122 (#126) | ✅ |
+| Engine cleanup: remove football terminology (#109) | #123 (#127) | ✅ |
+| Remove home_goals/away_goals from MatchReport (#111) | #123 (#127) | ✅ |
+| Replace legacy football engine with simulate_lol (#113) | #123 (#127) | ✅ |
+| SetPieceTakers → TeamRoles (#112) | #124 (#128) | ✅ |
+| Frontend position→role migration | #124 (#128) | ✅ |
+| Unwrap audit (production unwraps → expect) | #124 (#128) | ✅ |
+
+### Deuda Técnica Remanente (post-Fase 1)
+
+- ⚠️ **Componentes monolíticos frontend**: `ChampionDraft.tsx` (3.149 LOC), `MatchSimulation.tsx` (1.922 LOC)
+- ⚠️ **`lol_sim_v2.rs` test compilation**: funciones faltantes (6.281 LOC, pre-existing)
+- ⚠️ **JSON-en-TEXT**: modelo de datos en SQLite (6 campos en players)
+- ⚠️ **100+ warnings de clippy**: pre-existing en workspace, no blocking en CI
+- ⚠️ **19 RustSec advisories**: pre-existing, cargo audit non-blocking
+- ⚠️ **Football remnants cleanup**: `Position` enum (18 variants) en `domain/src/stats.rs`, `TraitContext::Foul`/`Goalkeeping` en `engine/shared.rs`, `fouls_committed` en legacy mirror de `stats_repo.rs`, y `"Draw"` handling legacy en `match_messages.rs` — todo backward compat que se puede eliminar en v0.3
---
## Fases del Roadmap
-### Fase 1: Limpieza y Foundation — Corto Plazo (v0.2 Alpha)
-
-**Objetivo:** Eliminar la deuda técnica de la transición fútbol→LoL y establecer las bases para desarrollo estable.
+### ✅ Fase 1: Hardening y Foundation — COMPLETADA (2026-05-02)
-**Prioridad:** 🔴 Alta
+**Objetivo:** Endurecer la seguridad, pagar deuda técnica crítica y establecer CI/CD sólido antes de agregar features.
-#### 🎯 Hitos
-
-- [ ] ✅ ~~Completar auditoría de documentación heredada~~ (existe: `INHERITED_DOCS_AUDIT.md`)
-- [ ] 🔲 Finalizar limpieza de nombres y estructuras de fútbol
-- [ ] 🔲 Documentar Provenance de datos heredados (`DATA_PROVENANCE.md` completo)
-- [ ] 🔲 Eliminar TODOs pendientes en `lol_sim_v2.rs`
-- [ ] 🔲 Establecer CI estable (resolver tests "experimentales")
+**Prioridad:** 🔴 Alta — **✅ 100% completado**
-#### 📋 Tareas
+#### 🎯 Hitos (todos ✅)
-- [ ] Renombrar tipos domain de "Player/Team/Football" a terminología LoL
-- [ ] Actualizar migraciones SQLite con prefijos o limpieza
-- [ ] Revisar `docs/legacy/inherited-docs/` y marcar lo obsoleto
-- [ ] Completar puerto de sistema de movimiento en lol_sim_v2.rs
-- [ ] Habilitar `cargo clippy` y `cargo test` en CI principal
-- [ ] Crear documento de migración de datos (fútbol → LoL)
-- [ ] **Migración de identidad**: `football_nation` → `nationality_code` + `competitive_region`
- - [ ] Crear migración SQL v028 (`RENAME COLUMN football_nation → nationality_code` + `ADD COLUMN competitive_region TEXT`)
- - [ ] Actualizar tipos Rust (`Player`, `Team`, `Manager`, `Staff`) con ambos campos
- - [ ] Actualizar frontend (tipos TypeScript, componentes UI, filtros por región)
- - [ ] Actualizar scripts de generación (`generate-lec-world.mjs`)
- - [ ] **Nota importante**: En LoL, "región" y "nacionalidad" son conceptos DISTINTOS:
- - `nationality_code` → país de origen del jugador (ej: "KR", "ES", "FR")
- - `competitive_region` → liga donde compite (ej: "LCK", "LEC", "LCS")
- - Un jugador coreano (`nationality_code: "KR"`) puede competir en `LEC`
+- ✅ **Seguridad**: CSP habilitado, path traversal eliminado en avatar endpoints, capabilities restringidas
+- ✅ **CI/CD endurecido**: `cargo audit`, `npm audit`, tests bloqueantes en core crates
+- ✅ **Tipos cross-stack**: `ts-rs` integrado con derives en 100+ tipos, feature-gated
+- ✅ **Tests legacy**: rotos marcados como `#[ignore]` con tracking issues, `continue-on-error` eliminado
+- ✅ **StateManager**: unificado en single `Mutex` con `with_session()`/`with_session_mut()`
-#### Métricas de Éxito
+#### PRs de Fase 1 (splits)
-- ✅ 0 TODOs activos en código de producción
-- ✅ 100% coverage en CI (no más "experimental")
-- ✅ Documentación heredada auditada y categorizada
+| PR | Issue | Descripción |
+|----|-------|-------------|
+| [#121](https://github.com/OpenLeagueManager/OLManager/pull/121) | [#125](https://github.com/OpenLeagueManager/OLManager/issues/125) | Champions, ts-rs, CI/CD, security, docs, StateManager, validation, AppError |
+| [#122](https://github.com/OpenLeagueManager/OLManager/pull/122) | [#126](https://github.com/OpenLeagueManager/OLManager/issues/126) | Domain cleanup, DB migrations V33-V42, football_nation removal, ofm_core adaptation |
+| [#123](https://github.com/OpenLeagueManager/OLManager/pull/123) | [#127](https://github.com/OpenLeagueManager/OLManager/issues/127) | Engine migration: football→LoL events, MatchConfig, simulate_lol |
+| [#124](https://github.com/OpenLeagueManager/OLManager/pull/124) | [#128](https://github.com/OpenLeagueManager/OLManager/issues/128) | Frontend position→role, SetPieceTakers→TeamRoles, fixes, unwrap audit |
---
-### Fase 2: Estabilización y Features Core — Mediano Plazo (v0.3 Beta)
+### Fase 2: Estabilización, Features Core y Release Beta — Mediano Plazo (v0.3 Beta)
-**Objetivo:** Implementar funcionalidades core del manager y estabilizar el producto para uso interno.
+**Objetivo:** Pagar deuda técnica restante de Fase 1, estabilizar simulación, implementar features core de gestión y release beta.
**Prioridad:** 🟡 Media
#### 🎯 Hitos
-- [ ] 🔲 Sistema de roster/plantel completo (contratar/despedir jugadores)
-- [ ] 🔲 Simulación de partidos funcional (más allá de LoL-sim v2)
-- [ ] 🔲 Sistema de finanzas (presupuesto, salarios, patrocinadores)
-- [ ] 🔲 Dashboard de estadísticas del equipo
-- [ ] 🔲 Primera release beta (v0.3.0-beta)
+- [ ] 🔲 **Fase 1 cleanup**: completar items que quedaron pendientes
+- [ ] 🔲 **Football remnants purge**: eliminar `Position` enum legacy, `TraitContext::Foul`/`Goalkeeping`, `fouls_committed` de stats_repo legacy mirror, y `"Draw"` handling en match_messages — dejar solo backward compat estrictamente necesario
+- [ ] 🔲 **Motor de simulación**: lol_sim_v2 compilando + live_match funcional
+- [ ] 🔲 **AppError + i18n**: migración completa de todos los comandos
+- [ ] 🔲 **Sistema de temporada completa**: Winter/Spring/Summer/Season Finals
+- [ ] 🔲 **Sistema de finanzas**: presupuesto, salarios, transferencias
+- [ ] 🔲 **Dashboard de estadísticas del equipo**
+- [ ] 🔲 **Release beta**: v0.3.0-beta taggeada y publicada
#### 📋 Tareas
-- [ ] Implementar modelo de jugador con stats LoL (KDA, rol, división)
-- [ ] Crear sistema de contratos y salarios
-- [ ] Desarrollar motor de simulación de partidos
-- [ ] Implementar sistema de calendario de temporadas
-- [ ] Añadir visualización de estadísticas en tiempo real
-- [ ] Configurar logging estructurado para debugging
-- [ ] Documentar API de comandos Tauri
+##### ✅ Phase 1: LoL Migration — COMPLETE
+
+- [x] **Engine crate cleanup (#109)**: terminología de fútbol eliminada del engine (EventType, TeamStats, MatchConfig, Snapshot, PlayerMatchStats, fouls.rs → eliminado, resolution.rs → eliminado)
+- [x] **Legacy engine reemplazado (#113)**: `engine::simulate()` → `simulate_lol()` basado en `LiveMatchState`
+- [x] **home_goals/away_goals eliminados (#111)**: campos redundantes quitados de `MatchReport`
+- [x] **SetPieceTakers → TeamRoles (#112)**: reemplazado en engine + domain + DB + frontend
+- [x] **Domain football fields eliminados (#114)**: `goals`/`yellow_cards`/`red_cards`/`fouls_committed` de `PlayerSeasonStats`
+- [x] **MatchRoles → TeamRoles**: V41 migration + domain rename + frontend types
+- [x] **V42 migration**: columnas muertas eliminadas de `teams` (`football_nation`, `match_roles`, `nationality_code`)
+- [x] **Seed data convertido**: `lec_world.json` posiciones de fútbol → roles LoL
+- [x] **Bug fixes post-migración**: role vs position (7 componentes frontend), PreMatchSetup, ChampionDraft, etc.
+- [x] **ts-rs typegen**: binary + derives para generación de tipos TypeScript
+
+##### 🧹 Fase 2 Cleanup (prioridad: 🔴 alta)
+
+- [ ] **Cross-stack type generation (#93)**: annotar ~58 tipos restantes con `#[derive(TS)]`, generar `bindings.ts`
+- [ ] **AppError full migration**: migrar todos los comandos (>50) de `Result` a `Result`
+- [ ] **Bug fixes pendientes**: #88 (split review), #84 (OVR formulas), #38 (player persistence), #39 (season progression), #37 (BO3 repeat), #35 (6-man roster), #33 (gold/items), #2 (MacOS)
+- [ ] **Pre-existing clippy cleanup**: resolver ~100 warnings heredados en workspace
+
+##### 🏗️ Arquitectura y DX (prioridad: 🟡 media)
+
+- [ ] **`tracing` migration**: reemplazar `log` por `tracing` + `tracing-subscriber` con spans por comando Tauri
+- [ ] **Logging config**: `Info` en release, `Debug` opt-in, rotación `KeepN(10)` (50 MB tope)
+- [ ] **Componentes monolíticos frontend**: romper `ChampionDraft.tsx` (3.149 LOC), `MatchSimulation.tsx` (1.922 LOC) en Container/Presentational
+- [ ] **`useEffect` audit**: activar `eslint-plugin-react-hooks/exhaustive-deps: error`, migrar fetch a TanStack Query
+- [ ] **Fix `ChampionRuntime` visibility**: warning `private_interfaces` en `lol_sim_v2.rs`
+- [ ] **Rust profile tuning**: añadir `[profile.release]` con LTO, strip, panic=abort
+
+##### 🎮 Gameplay Engine — LoL Simulation (prioridad: 🔴 alta)
+
+- [ ] **Sistema de ítems**: items afectan stats reales (AD, AP, armor, etc.)
+ - [ ] Struct `Item` con stats, costo, build path
+ - [ ] Auto-buy inteligente por rol
+ - [ ] Items de soporte con gold generation
+ - [ ] Componentes y items completos (recetas)
+- [ ] **Champion abilities diferenciadas**
+ - [ ] Pasiva + Q/W/E/R con scalings (AD/AP)
+ - [ ] Tipos de daño: físico, mágico, verdadero
+ - [ ] Ultimates con cooldown largo y momento decisivo
+ - [ ] Unique passives por champion
+- [ ] **Wave management + farmeo**
+ - [ ] Oleadas de minions cada 30s
+ - [ ] Last hit da gold (no solo gold pasivo)
+ - [ ] Congelar / pushear líneas como decisión táctica
+ - [ ] CS como métrica de rendimiento
+- [ ] **Jungla + objetivos neutros**
+ - [ ] Campamentos con respawn (Gromp, Wolves, Raptors, Krugs, Blue/Red)
+ - [ ] Pathing y ganks tempranos
+ - [ ] Dragones elementales (Infernal, Mountain, Cloud, Ocean, Hextech, Chemtech)
+ - [ ] Herald y Baron con buffs reales
+- [ ] **Sistema de visión**
+ - [ ] Wards trinket (amarilla) y control ward (rosa)
+ - [ ] Vision score como métrica
+ - [ ] Stealth y detección
+- [ ] **Power spikes por fase del juego**
+ - [ ] Early game (0-15 min): fase de líneas
+ - [ ] Mid game (15-30 min): rotaciones, objectives
+ - [ ] Late game (30+ min): team fights decisivos
+ - [ ] Escalado por nivel de champion
+
+##### 🎮 Features Core (prioridad: 🟡 media)
+
+- [ ] **Calendario de temporada**: implementar splits LEC (Winter/Spring/Summer) + Season Finals
+ - [ ] Generación de fixtures para Spring y Summer split
+ - [ ] Playoffs por split (top 6/8)
+ - [ ] Season Finals con Championship Points
+ - [ ] UI de calendario en Dashboard
+- [ ] **Sistema de finanzas**:
+ - [ ] Presupuesto por temporada (salary cap)
+ - [ ] Contratos multi-año con incrementos
+ - [ ] Renovaciones y cláusulas de rescisión
+ - [ ] Patrocinadores con objetivos
+- [ ] **Mercado de transferencias**:
+ - [ ] Ventana de transferencias (Offseason / Mid-season)
+ - [ ] Free agency con negociación
+ - [ ] Trades entre equipos
+ - [ ] UI de mercado en TransfersTab
+- [ ] **Modo espectador**: ver partidos sin interactuar (skip mode existente, pulir visualización)
+- [ ] **Dashboard de estadísticas**: visualizaciones de rendimiento del equipo (KDA, gold dif, visión, etc.)
+- [ ] **Staff management**: contratar/despedir coaches, scouts, analysts con efectos en gameplay
+- [ ] **Documentar API de comandos Tauri**: listado de comandos, params, returns
+
+##### 🧪 Testing (prioridad: 🟢 baja)
+
+- [ ] Añadir **Playwright** smoke tests (5 flujos críticos: crear → avanzar → simular → guardar → recargar)
+- [ ] Añadir **`proptest`** para propiedades del motor de simulación
#### Métricas de Éxito
-- ✅ Usuario puede crear equipo, gestionar roster y simular partido
-- ✅ Sistema de finances funcional (presupuesto > 0 después de gastos)
-- ✅ Release beta publicada y taggeada
+- ✅ Todos los comandos usan `AppError` con códigos i18n
+- ✅ `lol_sim_v2` compila y pasa tests
+- ✅ Usuario puede completar temporada completa (Winter→Spring→Summer→Season Finals)
+- ✅ Sistema de finanzas funcional (presupuesto > 0 después de gastos)
+- ✅ Ventana de transferencias operativa
+- ✅ `engine` crate sin terminología de fútbol (EventType, TeamStats, fouls.rs)
+- ✅ Release beta (v0.3.0-beta) taggeada y publicada
+- ✅ Logging estructurado con spans por comando
---
-### Fase 3: Ecosistema y Comunidad — Largo Plazo (v1.0 Stable)
+### Fase 3: Ecosistema y Distribución — Largo Plazo (v1.0 Stable)
-**Objetivo:** Construir ecosistema completo, abrir a comunidad y alcanzar estabilidad de producción.
+**Objetivo:** Construir ecosistema completo, abrir a comunidad, distribuir con actualizaciones automáticas y alcanzar estabilidad de producción.
**Prioridad:** 🟢 Baja
#### 🎯 Hitos
- [ ] 🔲 Sistema de scouting (buscar jugadores en el mercado)
-- [ ] 🔲 Competiciones y rankings (simular temporadas LEC-style)
-- [ ] 🔲 Modo multijugador básico (comparte equipos)
-- [ ] 🔲 Documentación completa para contribuyentes
+- [ ] 🔲 Competiciones y rankings multi-temporada
+- [ ] 🔲 **`tauri-plugin-updater`** con auto-update y firmas
+- [ ] 🔲 **Firma de binarios**: Windows EV + macOS Developer ID + GPG signatures
+- [ ] 🔲 **Perfil release optimizado**: LTO, codegen-units=1, strip, panic=abort
+- [ ] 🔲 Modo multijugador básico (compartir partidas)
- [ ] 🔲 Primera release estable (v1.0.0)
- [ ] 🔲 Publicación OSS (anuncio oficial)
@@ -136,17 +232,22 @@ OLManager es un manager de esports para League of Legends diseñado para simular
- [ ] Implementar mercado de transferencias
- [ ] Crear sistema de ligas/torneos con estadísticas
-- [ ] Añadir mode expansions (otras regiones: LCK, LCS, LPL)
+- [ ] Añadir otras regiones (LCK, LCS, LPL, PCS, VCS)
+- [ ] Configurar `tauri-plugin-updater` con endpoint en GitHub Releases
+- [ ] Firmar manifests con minisign/ed25519
+- [ ] Firmar Windows con certificado EV (DigiCert/SSL.com)
+- [ ] Notarizar macOS con Apple Developer ID
+- [ ] Publicar SHA256 de cada artefacto + GPG signature en el tag
+- [ ] Configurar `[profile.release]` con LTO, strip, panic=abort
- [ ] Desarrollar API REST pública (opcional)
-- [ ] Configurar containerización (Docker)
-- [ ] Setup CI/CD completo con releases automáticas
-- [ ] Escribir CONTRIBUTING.md
-- [ ] Audit de seguridad y hardening
+- [ ] Configurar containerización (Docker para simulación headless)
+- [ ] Escribir documentación completa para contribuyentes
#### Métricas de Éxito
+- ✅ v1.0.0 publicada con changelog y firmas
+- ✅ `tauri-plugin-updater` funcional (auto-update de alpha a stable)
- ✅ Comunidad puede contribuir siguiendo flow issue-first
-- ✅ v1.0.0 publicada con changelog completo
- ✅ docs/ actualizada para usuarios y desarrolladores
---
@@ -167,7 +268,7 @@ Siguiendo [`GOVERNANCE.md`](docs/GOVERNANCE.md), el desarrollo sigue este flujo:
| Categoría | Labels |
|-----------|--------|
| **Status** | `status:needs-review`, `status:approved` |
-| **Type** | `type:feature`, `type:bug`, `type:docs`, `type:chore`, `type:refactor`, `type:test`, `type:release` |
+| **Type** | `type:feature`, `type:bug`, `type:docs`, `type:chore`, `type:refactor`, `type:test`, `type:release`, `type:security` |
### Ramas
@@ -183,14 +284,14 @@ Siguiendo [`GOVERNANCE.md`](docs/GOVERNANCE.md), el desarrollo sigue este flujo:
| Fase | KPI Principal | KPI Secundario |
|------|---------------|----------------|
-| **Fase 1** | TODOs remaining: 0 | CI tests: 100% pass |
-| **Fase 2** | Features core: 5 | Beta users: N/A |
-| **Fase 3** | v1.0.0 released | OSS launch: done |
+| **Fase 1** | ✅ **Completada**. 9/9 issues, 4 PRs mergeados | CI tests: core crates pasan |
+| **Fase 2** | Features core: 6 (season, finances, transfers, sim, dashboard, staff) | Release beta publicada |
+| **Fase 3** | v1.0.0 released | Auto-updater funcional |
### Badges de Progreso
```markdown
-[](ROADMAP.md)
+[](ROADMAP.md)
[](ROADMAP.md)
[](actions)
```
@@ -200,6 +301,7 @@ Siguiendo [`GOVERNANCE.md`](docs/GOVERNANCE.md), el desarrollo sigue este flujo:
## Cómo Seguir el Progreso
- **Roadmap (este archivo)** — Estado general y fases
+- **`docs/proposals/analisis.md`** — Análisis técnico completo con 44 hallazgos detallados
- **GitHub Issues** — Tareas individuales con labels
- **GitHub Project Board** — Vista kanban del desarrollo
- **GitHub Releases** — Changelogs y downloads
@@ -237,7 +339,7 @@ npm run dev
cargo build --workspace
cargo test --workspace
-# full CI (experimental)
+# full CI
npm run test
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
@@ -249,11 +351,11 @@ cargo test --workspace
| Versión | Fecha | Notas |
|---------|-------|-------|
-| 0.1.1 | 2026-04-28 | Pre-alpha actual |
-| 0.2.0-alpha | ⏳ Pendiente | Alpha con deuda técnica resuelta |
-| 0.3.0-beta | ⏳ Pendiente | Beta con features core |
-| 1.0.0 | ⏳ Pendiente | Primera stable |
+| 0.1.2 | 2026-05-02 | Pre-alpha actual. **Fase 1 completada** (9/9 issues) |
+| 0.2.0-alpha | ⏳ Pendiente | Alpha con Phase 1 cleanup y Fase 2 features |
+| 0.3.0-beta | ⏳ Pendiente | Beta con features core + release |
+| 1.0.0 | ⏳ Pendiente | Primera stable con auto-updater |
---
-*Última actualización: 2026-04-29 — Actualizado con corrección de identidad (nationality_code + competitive_region)*
+*Última actualización: 2026-05-02 — Roadmap actualizado tras análisis técnico arquitectónico (`docs/proposals/analisis.md`)*
diff --git a/docs/proposals/analisis.md b/docs/proposals/analisis.md
new file mode 100644
index 000000000..8e449aca2
--- /dev/null
+++ b/docs/proposals/analisis.md
@@ -0,0 +1,444 @@
+# Análisis Técnico Arquitectónico — Open League Manager (OLManager)
+
+**Rol:** Arquitecto de Software / Lead Developer Senior
+**Versión analizada:** 0.1.2 (pre-alpha, GPL-3.0)
+**Fecha:** 2026-05-02
+**Repositorio:** OLManager (continuación de OpenFootManager)
+
+---
+
+## 0. Resumen del proyecto (real, tras revisión del código)
+
+OLManager **no** es una API de inventarios — es un **juego de gestión deportiva de escritorio** (League of Legends manager) construido con:
+
+| Capa | Tecnología | Tamaño aprox. |
+|---|---|---|
+| Frontend | React 19 + TypeScript + Vite + Tailwind 4 + Zustand 5 + react-router 7 + i18next | ~71.500 LOC TS/TSX, 228 componentes |
+| Backend | Rust + Tauri v2 (4 crates: `domain`, `engine`, `ofm_core`, `db`) + comandos `src-tauri/src` | ~77.000 LOC Rust, 173 archivos |
+| Persistencia | SQLite por partida (`rusqlite` + `rusqlite-migration`), 37 migraciones versionadas | — |
+| Tests | Vitest (107 tests frontend) + `cargo test` (tests por crate) | — |
+| CI/CD | GitHub Actions (`pr.yml` y `release.yml`) | — |
+
+**Arquitectura real:** monolito desktop con frontera IPC bien definida (Tauri commands), backend Rust dividido por *bounded contexts* en crates. La capa `domain` es model-only, `engine` se aísla para simulación, `ofm_core` orquesta gameplay y `db` aísla SQLite. La regla de dependencia documentada en `docs/ARCHITECTURE.md` es **correcta y deseable**.
+
+A continuación, el análisis sigue el formato **Problema encontrado → Solución sugerida**.
+
+---
+
+## 1. Arquitectura y Diseño
+
+### Problema 1.1 — Comandos Tauri convertidos en "god files"
+`src-tauri/src/commands/game.rs` tiene **2.291 líneas** y mezcla seeds de academia, parsing de fechas, slugify, lookups de nacionalidad y los propios comandos Tauri (`start_new_game`, `save_game`, `load_game`, `update_manager_profile`, etc.). Lo mismo en `src-tauri/src/application/lol_sim_v2.rs` con **6.281 líneas**.
+
+**Solución sugerida:**
+- Extraer del módulo `commands/game.rs` los helpers no-Tauri a un módulo `application/game_setup/` (parsing, seeds, slug). Mantener en `commands/game.rs` únicamente funciones `#[tauri::command]` (esperado: <300 líneas).
+- Romper `application/lol_sim_v2.rs` en submódulos por dominio (`combat.rs` ya existe — completar la separación: `economy`, `objectives`, `vision`, `events`, `state`).
+- Regla: **máximo 500 LOC por archivo Rust, 300 LOC por archivo TS/TSX**. Hacer cumplir con un check de CI (script simple en `pr.yml`).
+
+### Problema 1.2 — Componentes React monolíticos
+`ChampionDraft.tsx` (3.149 LOC), `MatchSimulation.tsx` (1.922 LOC), `LolMatchLive.tsx` (1.200 LOC), `PlayerProfile.tsx` (1.093 LOC). Estos componentes son contenedores con lógica de negocio, vistas, modales y orquestación de servicios.
+
+**Solución sugerida:**
+- Aplicar **Container/Presentational** y extraer hooks de vista (`useDraftReducer`, `useMatchControls`).
+- Mover lógica derivada/calculada a `lib/` o `*Helpers.ts` (el patrón ya existe — ej. `dashboardHelpers.ts`, `inboxHelpers.tsx`); usarlo de forma consistente.
+- Considerar `useReducer` o un slice de Zustand dedicado para estados con muchas transiciones (draft, live match) en lugar de `useState` apilados.
+
+### Problema 1.3 — Frontera frontend↔backend tipada manualmente
+Las DTOs Rust (`#[derive(Serialize)]`) y los tipos TS (`store/types.ts`, ~60 tipos exportados desde `gameStore.ts`) se mantienen en paralelo a mano. Cualquier cambio en Rust que olvide actualizar TS solo se nota en runtime.
+
+**Solución sugerida:**
+- Adoptar **`ts-rs`** o **`specta`** + `tauri-specta`: anota los tipos Rust con `#[derive(TS)]`/`#[derive(Type)]` y genera automáticamente `bindings.ts` consumido por el frontend.
+- Tipar también los nombres de comando para que `invoke("save_game")` deje de ser un string-literal y sea verificable en compilación.
+- Beneficio inmediato: cualquier cambio rompedor en una struct Rust falla en `build:types` antes de llegar a producción.
+
+### Problema 1.4 — Estado global en `StateManager` con `Mutex