# Plataforma de Promoción de Jugadores de Baloncesto
## Arquitectura Técnica — v1.0

---

## 1. Stack recomendado

Tu sugerencia de React + Firebase es acertada para este caso. Justificación y ajustes:

| Capa | Tecnología | Por qué |
|------|-----------|---------|
| Frontend | **React 18 + Vite** | Vite compila más rápido que CRA (deprecado). SPA con React Router para rutas dinámicas `/jugador/:slug` |
| Estilos | **Tailwind CSS 3** | Ideal para el diseño dark/neón con utilidades arbitrarias |
| Base de datos | **Firestore** | Lecturas rápidas, modelo documental encaja perfecto con "1 jugador = 1 documento" |
| Archivos | **Firebase Storage** | Fotos de perfil y galería |
| Auth | **Firebase Auth** (email/password) | Protege el dashboard. Solo admins registrados |
| Hosting | **Firebase Hosting** | SSL gratis, dominio custom, rewrites para SPA |
| Compresión img | `browser-image-compression` | Comprime en el navegador ANTES de subir (ahorra Storage y acelera landings) |
| QR | `qrcode.react` | Genera QR en el dashboard, descargable como PNG |
| PDF | `jspdf` + `html2canvas` (o `react-to-print`) | Dossier deportivo descargable desde la landing |
| Videos | Embeds de YouTube/Vimeo | No subir video a Storage (costo). Solo guardar el ID/URL |

> **Nota SEO**: Una SPA pura no indexa bien en Google ni genera previews en WhatsApp/redes.
> Si compartir en redes es crítico (lo es, para captar auspicios), considera migrar luego a
> **Next.js con SSR** desplegado en Vercel, manteniendo Firestore como backend. Para el MVP,
> React + react-helmet-async es suficiente.

---

## 2. Modelo de datos — Firestore

### Colección `players` (documento por jugador, ID = slug único)

```js
// players/{slug}  → ej: players/carlos-mendoza-2026
{
  // --- Identificación y control ---
  slug: "carlos-mendoza-2026",       // URL única: /jugador/carlos-mendoza-2026
  publicado: true,                    // Toggle: visible o borrador
  fechaCreacion: Timestamp,
  fechaActualizacion: Timestamp,
  creadoPor: "uid-del-admin",

  // --- Perfil Personal ---
  perfil: {
    nombre: "Carlos",
    apellido: "Mendoza",
    fechaNacimiento: "2004-03-15",    // Se calcula edad en cliente (no se desactualiza)
    alturaCm: 198,
    pesoKg: 92,
    envergaduraCm: 205,
    posicion: "Alero",                // Base | Escolta | Alero | Ala-Pívot | Pívot
    posicionSecundaria: "Escolta",    // opcional
    dorsal: 23,
    ciudad: "Machala",
    pais: "Ecuador",
    manoDominante: "Derecha",
    bio: "Texto corto de presentación..."
  },

  // --- Estadísticas (promedios por partido, temporada actual) ---
  stats: {
    ppg: 18.5,      // Puntos
    rpg: 7.2,       // Rebotes
    apg: 4.1,       // Asistencias
    spg: 1.8,       // Robos
    bpg: 0.9,       // Tapones
    ftPct: 82.0,    // % Tiros libres
    fg2Pct: 54.0,   // % Tiros de 2
    fg3Pct: 38.5,   // % Triples
    partidosJugados: 24,
    temporada: "2025-2026"
  },

  // --- Multimedia ---
  media: {
    fotoPerfil: "https://storage.../players/slug/perfil.webp",
    fotoHero: "https://storage.../players/slug/hero.webp",   // vertical, para parallax
    galeria: [
      { url: "https://...", caption: "Final provincial 2025" }
    ],
    videos: [
      { plataforma: "youtube", id: "dQw4w9WgXcQ", titulo: "Highlights Temporada 2025" },
      { plataforma: "vimeo", id: "123456789", titulo: "Mix defensivo" }
    ]
  },

  // --- Historial y Logros ---
  historial: {
    equipos: [
      { nombre: "Club Orense BBC", temporada: "2024-2026", liga: "Liga Provincial El Oro" }
    ],
    campeonatos: [
      { titulo: "Campeón Provincial Sub-21", anio: 2025 }
    ],
    premios: [
      { titulo: "MVP Final Cantonal", anio: 2025 }
    ]
  },

  // --- Contacto / Auspicios ---
  auspicio: {
    buscaTipos: ["Auspicio de marca", "Beca universitaria"],  // multi-select
    marcasActuales: ["Marca X"],
    emailContacto: "agente@correo.com",
    telefono: "+593...",
    whatsapp: "+593...",                // el CTA puede abrir wa.me directo
    instagram: "@jugador",
    dossierUrl: null                    // PDF pre-generado opcional
  },

  // --- Personalización visual de la landing ---
  tema: {
    colorAcento: "#CCFF00",            // neón por defecto o color del equipo
    colorSecundario: "#7B2FFF"
  }
}
```

**Decisiones clave del modelo:**

1. **ID del documento = slug**: lectura directa con `doc(db, 'players', slug)` — 1 sola lectura, sin queries. El slug se genera al crear (`nombre-apellido` + sufijo si colisiona).
2. **Todo en un documento**: una landing carga con **una única lectura de Firestore** (límite 1MB por doc es más que suficiente). No usamos subcolecciones para stats/media porque siempre se leen juntas.
3. **Subcolección solo para mensajes**: `players/{slug}/mensajes/{autoId}` guarda los envíos del formulario de contacto (crece indefinidamente, no debe inflar el doc principal).

### Subcolección `players/{slug}/mensajes`

```js
{
  nombre: "Reclutador Y",
  organizacion: "Universidad Z",
  email: "scout@uni.edu",
  tipoInteres: "Beca",          // Beca | Auspicio | Contrato | Otro
  mensaje: "...",
  fecha: Timestamp,
  leido: false
}
```

### Colección `admins`

```js
// admins/{uid} — existencia del doc = permiso de admin
{ email: "chucky@...", rol: "superadmin" }
```

### Reglas de seguridad (`firestore.rules`)

```js
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    function esAdmin() {
      return request.auth != null
        && exists(/databases/$(database)/documents/admins/$(request.auth.uid));
    }

    match /players/{slug} {
      allow read: if resource.data.publicado == true || esAdmin();
      allow write: if esAdmin();

      match /mensajes/{msgId} {
        allow create: if true;          // público puede escribir (formulario CTA)
        allow read, update, delete: if esAdmin();
      }
    }

    match /admins/{uid} {
      allow read: if request.auth.uid == uid;
      allow write: if false;            // solo desde consola Firebase
    }
  }
}
```

### Storage — estructura de rutas

```
/players/{slug}/perfil.webp
/players/{slug}/hero.webp
/players/{slug}/galeria/{timestamp}.webp
```

Reglas: escritura solo autenticados, lectura pública, límite 5MB por archivo.

---

## 3. Estructura de componentes React

```
basket-scout/
├── src/
│   ├── main.jsx                      # Entry point + Router
│   ├── App.jsx                       # Rutas: /admin/*, /jugador/:slug
│   │
│   ├── lib/
│   │   └── firebase.js               # Init de app, db, storage, auth
│   │
│   ├── hooks/
│   │   ├── usePlayer.js              # Fetch de jugador por slug (con caché)
│   │   └── useAuth.js                # Estado de sesión admin
│   │
│   ├── utils/
│   │   ├── imageUpload.js            # Compresión + subida a Storage
│   │   ├── slug.js                   # Generación de slug único
│   │   └── pdfDossier.js             # Exportación a PDF
│   │
│   ├── pages/
│   │   ├── admin/
│   │   │   ├── Login.jsx
│   │   │   ├── PlayerList.jsx        # Tabla de jugadores + acciones
│   │   │   └── PlayerEdit.jsx        # Contiene <PlayerForm />
│   │   ├── PlayerLanding.jsx         # Landing pública /jugador/:slug
│   │   └── NotFound.jsx
│   │
│   ├── components/
│   │   ├── dashboard/
│   │   │   ├── PlayerForm.jsx        # ⭐ Formulario principal (entregable 3)
│   │   │   ├── ImageUploader.jsx     # Drag&drop con preview y compresión
│   │   │   ├── GalleryManager.jsx    # CRUD de fotos de galería
│   │   │   ├── QRGenerator.jsx       # QR de la URL única, descargable
│   │   │   └── MessagesInbox.jsx     # Bandeja de mensajes de reclutadores
│   │   │
│   │   ├── landing/
│   │   │   ├── HeroSection.jsx       # Parallax + nombre gigante
│   │   │   ├── StatsSection.jsx      # Tarjetas de estadísticas
│   │   │   ├── MediaGallery.jsx      # Carrusel + videos embebidos
│   │   │   ├── HistorySection.jsx    # Equipos, campeonatos, premios
│   │   │   └── ContactCTA.jsx        # CTA + formulario de contacto
│   │   │
│   │   └── shared/
│   │       ├── ProtectedRoute.jsx    # Guard de rutas admin
│   │       └── Spinner.jsx
│   │
│   └── index.css                     # Tailwind + fuentes + keyframes
│
├── firestore.rules
├── storage.rules
├── tailwind.config.js
└── firebase.json                     # Hosting + rewrites SPA
```

### Rutas (React Router)

```jsx
<Route path="/admin/login" element={<Login />} />
<Route path="/admin" element={<ProtectedRoute><PlayerList /></ProtectedRoute>} />
<Route path="/admin/jugador/:slug?" element={<ProtectedRoute><PlayerEdit /></ProtectedRoute>} />
<Route path="/jugador/:slug" element={<PlayerLanding />} />
```

---

## 4. Dependencias

```bash
npm create vite@latest basket-scout -- --template react
cd basket-scout
npm i firebase react-router-dom browser-image-compression qrcode.react jspdf html2canvas react-helmet-async
npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

---

## 5. Flujo de los 3 consejos implementados

**Compresión de imágenes** (`utils/imageUpload.js`): antes de subir, `browser-image-compression`
convierte a WebP, máx 1600px de ancho y ~0.4MB para galería / 0.25MB para perfil. La landing
carga fotos livianas sin perder calidad visible.

**PDF Dossier** (`utils/pdfDossier.js`): genera un currículum deportivo A4 con jsPDF usando
directamente los datos de Firestore (no captura de pantalla → texto seleccionable y nítido).
El botón "Descargar Dossier" vive en el CTA de la landing.

**Código QR** (`components/dashboard/QRGenerator.jsx`): en el dashboard, junto a cada jugador,
se renderiza el QR de `https://tudominio.com/jugador/{slug}` con `qrcode.react`, exportable
como PNG en alta resolución (1000px) para imprimir en camisetas o tarjetas.
