Sube documentacion del proyecto

This commit is contained in:
FrancoRu
2026-09-18 13:30:39 -03:00
parent df28ce57d2
commit bbb09554bb
3 changed files with 1561 additions and 0 deletions
+315
View File
@@ -0,0 +1,315 @@
# Despliegue — CI/CD con GitHub Actions
Este documento describe cómo quedan configurados el pipeline de build/publish a GHCR y el
despliegue automático en el servidor privado, y qué pasos manuales (una sola vez) hacen falta
para que funcione.
Los workflows viven en `.github/workflows/deploy-backend.yml` y
`.github/workflows/deploy-frontend.yml`. Cada uno tiene dos jobs: `build` (en un runner de GitHub,
compila y publica la imagen en GHCR) y `deploy` (en el runner self-hosted del servidor, la baja y
reinicia el contenedor correspondiente). Un push a `develop` que solo toca `Club12-Backend/**`
dispara únicamente el workflow de backend, y viceversa; un cambio en `docker-compose.yml` o en los
workflows dispara ambos.
## 1. Prerrequisitos del servidor
- Host Debian con Docker Engine + Docker Compose v2 instalados (`docker compose version` debe
responder `v2.x`).
- El usuario que corre el runner (`ghrunner`) debe pertenecer al grupo `docker`:
```bash
sudo usermod -aG docker ghrunner
# cerrar sesión y volver a entrar (o `newgrp docker`) para que el cambio de grupo tome efecto
```
Verificar como ese usuario, sin `sudo`:
```bash
docker ps
```
Si falla por permisos, el grupo no se aplicó todavía.
## 2. Registrar los runners self-hosted (manual, una vez por runner)
Se necesitan **dos runners registrados por separado**, uno por cada `runs-on` label. Un mismo
runner no puede sostener dos jobs en simultáneo.
Obtener un token de registro en:
`https://github.com/FrancoRu/Taller-de-Integracion/settings/actions/runners/new`
Backend, en `/home/ghrunner/actions-runner` (o el directorio equivalente):
```bash
./config.sh --url https://github.com/FrancoRu/Taller-de-Integracion \
--token <REGISTRATION_TOKEN> \
--name club12-back \
--labels Club-12-back-runner \
--work _work
sudo ./svc.sh install ghrunner && sudo ./svc.sh start
```
Repetir en un **segundo directorio de runner, separado**, con:
```bash
./config.sh --url https://github.com/FrancoRu/Taller-de-Integracion \
--token <REGISTRATION_TOKEN> \
--name club12-front \
--labels Club-12-front-runner \
--work _work
sudo ./svc.sh install ghrunner && sudo ./svc.sh start
```
Notas importantes:
- `self-hosted` se agrega automáticamente al registrar el runner — **no** hay que pasarlo en
`--labels`.
- Los strings de label son sensibles a mayúsculas/minúsculas y deben coincidir exactamente con los
workflows: `Club-12-back-runner` y `Club-12-front-runner`.
- Ambos runners corren en el mismo host (`192.168.0.200`), sobre el mismo proyecto de compose
(`club12`) — por diseño, para que ambos jobs de `deploy` puedan compartir el mismo directorio de
compose sin pisarse.
## 3. Bootstrap del `.env` de producción (manual, una sola vez)
El pipeline **nunca** crea, lee ni sobrescribe el `.env` de producción. Si falta, el paso de
sincronización del job `deploy` falla de forma explícita y detiene el workflow.
```bash
mkdir -p /home/docker-compose/Club12
cp .env.example /home/docker-compose/Club12/.env
# completar cada valor CHANGE_ME en /home/docker-compose/Club12/.env
chmod 600 /home/docker-compose/Club12/.env
```
Este archivo vive de forma permanente en `/home/docker-compose/Club12/`, un directorio distinto al
workspace efímero que usa `actions/checkout` — por eso `git clean -ffdx` del checkout nunca lo
toca.
## 4. Visibilidad del paquete en GHCR
Los paquetes de GHCR son **privados por defecto**, incluso en un repositorio público. El job de
`deploy` ya hace `docker login ghcr.io` con un token con permiso `read:packages`, así que el
`pull` funciona sin cambios adicionales. Alternativa: si se prefiere evitar el login, se puede
cambiar la visibilidad del paquete a público desde la UI de GHCR
(`Package settings → Change visibility`).
## 5. Rollback manual
Si un deploy deja el servicio en mal estado, se puede volver a la imagen anterior directamente en
el servidor, sin pasar por GitHub:
```bash
cd /home/docker-compose/Club12
docker tag ghcr.io/francoru/club12-backend:previous ghcr.io/francoru/club12-backend:latest
docker compose up -d --no-deps --no-build backend
```
(Reemplazar `backend` por `frontend` y el nombre de imagen correspondiente para el frontend.)
## 6. Desarrollo local (sin cambios)
`docker-compose.yml` conserva los bloques `build:` como fallback local — el pipeline de CI nunca
los usa (`--no-build` en cada deploy), pero siguen funcionando para levantar el proyecto en la
máquina de desarrollo:
```bash
docker compose build && docker compose up -d
```
En un host que no sea Linux, la ruta absoluta del bind mount del servicio `db`
(`/home/docker/club12/db`) no aplica: usar un `docker-compose.override.yml` (ignorado por
git) que reemplace esa línea de volumen por un volumen nombrado local.
## 7. Migración a Postgres self-hosted (manual, una sola vez)
A partir del cambio `selfhosted-postgres-db`, `docker-compose.yml` incluye un servicio
`db` (`postgres:17-alpine`) en la red interna `club12`, **sin puerto publicado**. El
backend deja de hablar con Supabase Postgres y pasa a usar ese contenedor. Supabase
**Storage** (buckets de imágenes y fichas médicas) se sigue usando igual — no se toca.
El pipeline de CI **no** levanta `db` (`deploy-backend.yml` usa `--no-deps`). El servicio
se levanta una vez en el cutover y se mantiene solo por `restart: unless-stopped`.
### 7.1 Preparar el host
```bash
# Directorio de datos de Postgres — en /home (420 GB), NO en la partición raíz (31 GB)
sudo mkdir -p /home/docker/club12/db
sudo chown 999:999 /home/docker/club12/db # uid del usuario postgres en la imagen alpine
# Directorio de backups
sudo mkdir -p /home/docker/backups/club12
```
### 7.2 Completar el `.env` de producción
En `/home/docker-compose/Club12/.env` (owner `gh-runner:gh-runner`, mode 600) agregar las
tres claves nuevas y reescribir la connection string. Elegir la contraseña ahora
(`openssl rand -base64 24`); `Username`/`Password` de la connection string **deben
coincidir** con `POSTGRES_USER`/`POSTGRES_PASSWORD`.
```bash
POSTGRES_USER=postgres
POSTGRES_DB=postgres
POSTGRES_PASSWORD=<contraseña-fuerte-elegida-ahora>
# reemplaza el valor anterior (pooler de Supabase):
ConnectionStrings__DbConnection=Host=db;Port=5432;Database=postgres;Username=postgres;Password=<misma-contraseña>;SSL Mode=Disable
# activa el backup periódico + restore-desde-panel ya incluido en la app
# (reemplaza al cron manual — ver §7.5):
Backup__Enabled=true
```
> Las variables `POSTGRES_*` sólo inicializan la base en el **primer arranque** del
> contenedor (directorio de datos vacío). Después, para cambiar la contraseña hay que
> hacerlo con `ALTER USER` dentro de la base.
### 7.3 Dump fresco de Supabase
Desde el host, usando `postgres:17-alpine` como cliente (no depende de la versión de
`pg_dump` del host). Los datos del app viven en **dos** schemas: `public` (Identity /
`AspNet*`) y `Club12` (todo lo demás). Pasá las credenciales como variables de entorno
para no pelear con el URL-encoding de la contraseña; sacá los valores del `.env` actual
(`grep -i DbConnection /home/docker-compose/Club12/.env`) — `User Id=` → `PGUSER`,
`Password=` → `PGPASSWORD`, `Server=` → `PGHOST`.
```bash
set +H # bash: desactiva la expansión de '!' en la password
cd /home/docker/backups/club12
docker run --rm -v "$PWD:/out" \
-e PGHOST=aws-1-us-east-2.pooler.supabase.com -e PGPORT=5432 -e PGDATABASE=postgres \
-e PGUSER='<User Id>' -e PGPASSWORD='<Password>' -e PGSSLMODE=require \
postgres:17-alpine \
pg_dump --format=custom --no-owner --no-privileges \
--schema=public --schema='"Club12"' \
--file=/out/club12-cutover-$(date +%F).dump
```
> `--schema='"Club12"'` **con comillas dobles adentro**: `pg_dump` pasa los patrones de
> `--schema` a minúsculas, así que `--schema=Club12` no matchea nada y el dump sale con
> solo la mitad de las tablas.
Verificá que estén los dos schemas:
```bash
docker run --rm -v "$PWD:/out" postgres:17-alpine \
pg_restore --list "/out/club12-cutover-$(date +%F).dump" | grep "TABLE DATA"
```
Tenés que ver ~22 tablas en `Club12` (`Teams`, `Players`, `Tournaments`, `Matches`, …) y
8 en `public` (`AspNetUsers`, `__EFMigrationsHistory`, …).
### 7.4 Cutover (ventana corta — el backend se reinicia)
```bash
cd /home/docker-compose/Club12
DUMP=/home/docker/backups/club12/club12-cutover-$(date +%F).dump
# 1. Levantar SOLO la base y esperar a que esté healthy
docker compose up -d db
docker compose ps db # STATUS = healthy
# 2. Restaurar el dump dentro del contenedor
docker compose cp "$DUMP" db:/tmp/restore.dump
docker compose exec -T db pg_restore --no-owner --no-privileges -U postgres -d postgres /tmp/restore.dump
docker compose exec -T db rm /tmp/restore.dump
# El único error esperado es `schema "public" already exists` (1, ignorado) — benigno.
# Cualquier otro error rojo: frená y revisá antes de seguir.
# 3. Verificación
docker compose exec -T db psql -U postgres -d postgres -c '\dt "Club12".*'
docker compose exec -T db psql -U postgres -d postgres -c 'select count(*) from "Club12"."Teams";'
docker compose exec -T db psql -U postgres -d postgres -c 'select count(*) from "public"."AspNetUsers";'
# 4. Con el .env ya editado (7.2 — cambiar ConnectionStrings__DbConnection a Host=db;…),
# recrear el backend
docker compose up -d backend
docker compose logs --tail=40 backend # sin errores de migración, "Now listening on: http://[::]:8080"
# 5. Bouncear el frontend: su nginx tiene cacheada la IP vieja del backend → si no,
# todo /api/* da 502 hasta reiniciarlo.
docker compose restart frontend
# 6. Verificar (el backend no está publicado en el host — va por el proxy :5001)
docker compose ps
curl -s -o /dev/null -w 'http=%{http_code}\n' 'http://localhost:5001/api/tournaments?pageSize=300'
```
En el arranque, `MigrateAsync` ve el schema ya restaurado y no hace nada (o aplica sólo
migraciones más nuevas que el dump). El seed está en `Seed:Enabled=false` en producción.
> El paso 5 (`restart frontend`) queda automatizado en `deploy-backend.yml` para los
> deploys de CI; en el cutover manual hay que hacerlo a mano.
### 7.5 Backup automático (feature integrado del backend)
El backend ya trae un sistema de backups completo — no hace falta cron ni script en el
host. Se activa con la variable agregada en §7.2:
```bash
Backup__Enabled=true
# opcionales, si se quiere otra cadencia (defaults entre paréntesis):
# Backup__IntervalHours=24 (24)
# Backup__RetentionCount=7 (7)
```
Con `Backup__Enabled=true`, un `DatabaseBackupHostedService` corre dentro del propio
proceso backend cada `Backup__IntervalHours` horas, genera el dump con `pg_dump` (ya
instalado en la imagen del backend) y lo guarda en el volumen `backup-data`
(`Backup__LocalStoragePath`, default `/app/backups`), podando al `Backup__RetentionCount`
más reciente.
- **Ver los backups:** panel admin → sección de backups. Cada fila muestra su origen
("Programado" para los automáticos, "Manual" para los que se disparan a mano desde el
mismo panel).
- **Restaurar:** botón de restore en el panel (`POST /api/backups/{id}/restore`) — sin
SSH al servidor. Antes de restaurar, la app crea automáticamente un backup de
seguridad del estado actual.
- **Verificar la integridad de un dump sin arriesgar producción:** descargarlo desde el
panel y restaurarlo en una base "scratch" separada (no usar el botón de restore de
producción solo para testear que un dump sirve).
### 7.6 Rollback
```bash
# En el .env, volver ConnectionStrings__DbConnection al valor del pooler de Supabase
docker compose up -d backend
```
No hay migración de schema que revertir. Dejar el contenedor `db` y
`/home/docker/club12/db` en su lugar hasta confirmar que el cutover quedó estable; recién
ahí dar de baja el proyecto de Supabase (los buckets de Storage siguen en uso).
### 7.7 `docker compose down` — cuidado
Los datos de `db` son un bind mount: sobreviven `down` y `down -v`. Aun así, `-v`
elimina los volúmenes nombrados (`backup-data`), y borrar `/home/docker/club12/db` a mano
destruye la base.
## Checklist post-merge
Estos pasos **no están cubiertos por el cambio de código en sí** — son responsabilidad del
usuario/operador una vez que este cambio se mergea a `develop`, y deben verificarse a mano porque
ningún runner self-hosted existe todavía en este momento:
- [ ] Registrar ambos runners con las labels exactas `Club-12-back-runner` y
`Club-12-front-runner` (sección 2).
- [ ] Crear el `.env` real en `/home/docker-compose/Club12/.env` a partir de `.env.example`
(sección 3).
- [ ] Hacer un push chico a `develop` que solo toque `Club12-Backend/**` y confirmar que:
- solo corre `deploy-backend.yml` (no `deploy-frontend.yml`);
- el job `build` publica `ghcr.io/francoru/club12-backend:latest`;
- el job `deploy` corre después de que `build` termina, en el runner `Club-12-back-runner`;
- `docker images` muestra `club12-backend:previous` (o el mensaje de "primer deploy" si es la
primera vez);
- `docker compose ps backend` muestra el contenedor recreado y saludable;
- el contenedor de `frontend` **no** se reinicia.
- [ ] Repetir el mismo push de prueba tocando solo `Club12-WebClient/**` y confirmar el mismo
comportamiento en espejo para `deploy-frontend.yml`.
- [ ] Confirmar que `/home/docker-compose/Club12/.env` sigue existiendo después del deploy y que
el servicio toma sus valores (por ejemplo, que la API arranca sin errores de configuración).
- [ ] Confirmar que `docker image prune -f` no deja crecer una pila de imágenes colgantes, y que
las tags `:latest` y `:previous` sobreviven.
+179
View File
@@ -0,0 +1,179 @@
# Manual de Usuario — Liga Club12
Guía de uso del sistema para administradores y visitantes. Para información técnica (arquitectura, instalación, requisitos cubiertos) ver [README.md](./README.md).
## 1. Perfiles de usuario
El sistema tiene dos tipos de acceso:
- **Visitante (sin cuenta)**: puede consultar toda la información pública de la liga desde la página principal, sin necesidad de registrarse ni iniciar sesión.
- **Usuario administrador (con cuenta)**: accede al panel privado para cargar y mantener toda la información de las temporadas y torneos. Los roles definen qué secciones puede administrar cada usuario.
## 2. Vista pública (sin iniciar sesión)
El menú principal tiene las secciones: **Inicio**, **Temporadas**, **Campeones**, **Sanciones**, **Novedades**, **Quiénes somos** e **Información** (con las sub-páginas **Ficha médica** y **Reglamento**).
La navegación de la competición sigue el orden **Temporada → Torneo → División**: no hay un listado plano de "torneos" — se entra por la temporada, se elige el torneo dentro de ella y ahí se accede a cada división.
Desde estas secciones, cualquier visitante puede:
- Ver la lista de **temporadas** y, dentro de cada una, sus **torneos** (activos y finalizados).
- Consultar los **equipos** participantes de cada división, con su plantel de jugadores.
- Ver el **fixture** y los **resultados** de los partidos jugados y a jugarse.
- Consultar la **tabla de posiciones** de cada división/etapa. El equipo en 1er puesto se destaca con una corona apenas toma la punta — no hace falta que termine la fase para verlo.
- Ver la **tabla de goleadores** (limitada a los 10 primeros en la vista pública).
- Ver las **llaves de eliminación** ("Llaves") como un árbol de bracket, con los cruces de cada ronda hasta la final.
- **Imprimir** la tabla de posiciones de una división.
- Consultar las **sanciones vigentes** aplicadas a jugadores.
- Leer las **novedades** publicadas en el blog de la liga.
- Ver la sección **Campeones**: el historial de campeones por división/copa de los torneos ya finalizados.
- Leer **Quiénes somos** y, dentro de **Información**, la página de **Ficha médica** (qué es y por qué se pide) y el **Reglamento** de la liga.
No se requiere ninguna acción de registro para acceder a esta información.
### 2.1 Ver las llaves de eliminación
1. Entrar a la división deseada dentro de un torneo.
2. Ir a la pestaña **"Llaves"** (junto a "Partidos" y "Posiciones").
3. El sistema muestra el árbol de la fase eliminatoria, incluyendo — cuando el torneo tiene más de una copa (por ejemplo Copa Oro y Copa Plata, según el rango de posiciones de la fase de grupos) — cada copa por separado.
4. Si un partido todavía no se jugó, ese cruce se muestra como "A definir" (TBD). Cuando la cantidad de equipos no es potencia de 2 (por ejemplo 5, 6 o 7 equipos), los mejores puestos de la fase de grupos reciben "bye" (pasan directo a la siguiente ronda) — es un comportamiento esperado, no un error. Si el sistema no puede determinar con certeza qué equipo avanza a la siguiente ronda, muestra las rondas en columnas sin línea de conexión en vez de arriesgar una conexión incorrecta.
### 2.2 Imprimir posiciones
1. Entrar a la vista de **posiciones** de una división.
2. Usar el botón **"Imprimir"**.
3. Se abre el diálogo de impresión del navegador (permite imprimir en papel o guardar como PDF desde ahí); la hoja se genera sin el menú ni otros elementos de navegación, solo la tabla de posiciones.
## 3. Iniciar sesión
1. Hacer clic en "Iniciar sesión" desde la página principal.
2. Ingresar correo electrónico y contraseña.
3. Si es la primera vez que se ingresa con una cuenta creada por un administrador, el sistema puede solicitar cambiar la contraseña antes de continuar.
4. Si se olvidó la contraseña, usar la opción "Recuperar contraseña": el sistema envía un correo con las instrucciones para restablecerla.
Al iniciar sesión correctamente, el sistema habilita el menú del panel administrativo según el rol del usuario.
## 4. Panel administrativo
El menú del panel se organiza en los siguientes grupos:
- **Competición**: Temporadas, Sanciones, Canchas.
- **Gestión de equipos**: Equipos, Jugadores.
- **Novedades** (blog).
- **Usuarios**.
- **Sistema**: Estadísticas, Registro de auditoría, Administración de datos.
- **Configuración**: Cambiar contraseña, Editar perfil.
Los torneos, divisiones, fases y partidos no tienen un ítem propio en el menú: se llega a ellos entrando a una temporada y navegando hacia adentro (Temporada → Torneo → División → Fase/Partido).
### 4.1 Temporadas, torneos y divisiones
1. **Crear una temporada**: agrupa uno o más torneos (por ejemplo, Apertura y Clausura del mismo año).
2. **Crear un torneo** dentro de la temporada: cargar nombre, fecha de inicio y demás datos generales.
3. **Crear divisiones** dentro del torneo (por ejemplo, categorías por edad, nivel o género).
4. **Inscribir equipos** a cada división.
5. **Generar la etapa** del torneo: el sistema arma automáticamente la fase de grupos y, una vez que termina, las llaves de eliminación directa según la cantidad de equipos clasificados (con "bye" a los mejores puestos si esa cantidad no es potencia de 2). Si el torneo define más de una copa (por ejemplo Copa Oro para los primeros puestos y Copa Plata para el resto), el sistema arma cada bracket por separado, tomando la posición de arranque de cada copa como el "1er sembrado" de su propio cuadro.
6. Una zona puede dividirse en **varios sub-grupos** (por ejemplo "Grupo A" y "Grupo B" dentro de la misma zona) para repartir muchos equipos en fases de grupos más chicas. Una zona con 2 o más sub-grupos siempre necesita al menos una copa configurada: sin ella no habría manera de determinar un campeón entre los sub-grupos, así que el sistema rechaza guardar esa combinación.
7. El torneo no puede pasar a **"En curso"** si alguna zona tiene menos de 2 equipos o si algún equipo inscripto tiene menos de 4 jugadores habilitados (ver 4.3) — el sistema avisa qué falta corregir antes de poder arrancar.
### 4.2 Equipos
Esta pestaña lista **clubes**: la identidad estable de una institución (por ejemplo "Central Entrerriano"), que se mantiene igual de una temporada a la otra. Un club agrupa todos sus equipos por temporada — así un club que jugó varios años aparece **una sola vez** en la lista, en vez de una fila repetida por cada temporada en la que participó.
- **Alta de un equipo nuevo**: nombre, código de tres letras, color de camiseta, escudo. Si ya existe un club con ese nombre, el equipo nuevo queda vinculado a él automáticamente; si el nombre no existe todavía, el sistema crea el club correspondiente.
- Búsqueda y filtrado por nombre de club.
- **Ver** un club abre su historial: la lista de sus equipos, uno por cada temporada/torneo en el que se inscribió, con el código de tres letras y el torneo correspondiente de cada uno.
- **Eliminar** un club solo está permitido si no tiene ningún equipo ni escuadra vinculada; si todavía tiene historial, el sistema rechaza el borrado.
- Desde el historial del club se puede eliminar un equipo puntual (una temporada específica) sin afectar el resto del historial del club.
- Cuerpo técnico: cargar el director técnico y demás staff se hace desde el equipo de esa temporada (dentro del historial del club), no desde la lista de clubes.
**Escuadras (club matriz)**: cuando una institución tiene más de un equipo dentro de la misma temporada (por ejemplo "Echagüe A" y "Echagüe B"), cada uno se da de alta como su propio club. Para agruparlos bajo la institución:
1. Entrar al historial de la escuadra (por ejemplo "Echagüe B").
2. Elegir el club matriz en **"Vincular con club matriz"** y confirmar con **"Vincular"**.
3. El historial de la institución matriz pasa a listar cada escuadra vinculada, y el de la escuadra muestra un enlace a su institución.
La vinculación es de un solo nivel: una institución no puede a su vez ser escuadra de otro club, y un club que ya tiene sus propias escuadras no puede pasar a ser escuadra de otro. Un club vinculado puede **desvincularse** en cualquier momento desde su propio historial.
### 4.3 Jugadores
- Alta de un jugador: datos personales, DNI (único por jugador), equipo al que pertenece.
- Edición o baja de jugadores.
- Búsqueda y filtrado.
- Número de camiseta (dorsal): se asigna por equipo y temporada, no es fijo para el jugador — puede cambiar si pasa a otro equipo o temporada.
**Ficha médica y habilitación**: para que un jugador pueda sumar puntos en un partido tiene que estar **habilitado** para esa temporada:
1. Subir el PDF de la ficha médica del jugador (por equipo y temporada).
2. Un administrador la revisa y la **aprueba** o **rechaza**. Solo queda habilitado cuando está aprobada y tiene un archivo real cargado — una aprobación sin archivo no habilita a nadie.
3. Una vez aprobada, la ficha no se puede volver a subir (queda de solo lectura); si hace falta corregirla, primero hay que rechazarla.
4. La habilitación es específica de esa temporada: un jugador que jugó habilitado el año pasado empieza la temporada nueva sin habilitar, aunque no haya cambiado de equipo.
### 4.4 Partidos
- Los partidos de fase de grupos y de eliminación se generan automáticamente al crear la etapa.
- Cargar el **resultado** de un partido una vez jugado, planilla por planilla: se cargan los puntos de cada jugador de ambos equipos y el resultado final se calcula sumándolos (no se tipea aparte). El sistema actualiza automáticamente la tabla de posiciones y de goleadores.
- Reglas al cargar el resultado:
- Solo pueden sumar puntos jugadores **habilitados** y sin sanción activa. Si algún jugador cargado no cumple esto, el sistema rechaza el guardado y lista, con nombre y equipo, a todos los jugadores con problema (no solo el primero).
- Un equipo necesita **al menos 4 jugadores habilitados** para que se le pueda cargar un resultado normal; si no llega a ese mínimo, el partido tiene que cargarse como **walkover** ("Marcar W.O.") en lugar de una planilla común.
- No se permiten empates: en fase de grupos el desempate en la tabla es PTS → PG (partidos ganados) → DG (diferencia de gol) → resultado entre los propios equipos empatados → DG de esos cruces; en fase eliminatoria, si el partido termina empatado, se juega tiempo suplementario.
- **Walkover**: un administrador puede marcar un partido como walkover indicando qué equipo se presentó — el sistema carga el resultado reglamentario a favor de ese equipo.
- Consultar el estado de cada partido (programado / jugado / walkover).
### 4.5 Estadísticas y goleadores
- Las estadísticas de puntos por jugador se cargan como parte de la planilla del resultado del partido (ver 4.4), no por separado.
- El sistema arma automáticamente la tabla de goleadores del torneo a partir de esta carga.
### 4.6 Sanciones
1. Registrar una sanción a un jugador (tipo, motivo, duración).
2. El jugador o su equipo puede presentar una **apelación** desde el sistema.
3. Un administrador revisa la apelación y la marca como **aceptada** o **rechazada**.
4. Si es aceptada, la sanción se levanta; si es rechazada, la sanción sigue vigente.
5. Un jugador sancionado no puede sumar puntos en un partido mientras la sanción esté activa (ver 4.4).
### 4.7 Canchas (Venues)
- Alta, edición y baja de las canchas/sedes donde se juegan los partidos.
- Al programar un partido, dos partidos en la misma cancha necesitan al menos 2 horas de diferencia entre sí.
### 4.8 Usuarios
- Alta de nuevos usuarios administradores, con asignación de rol.
- Activar o desactivar cuentas existentes.
- Un usuario puede cambiar su propia contraseña o editar su perfil desde **Configuración**.
### 4.9 Novedades (Blog)
- Publicar noticias o crónicas de partidos, visibles en la vista pública.
- Editar o eliminar publicaciones existentes.
- Una publicación puede guardarse como borrador antes de hacerla visible al público.
### 4.10 Sistema
- **Estadísticas**: panel con métricas generales de uso/carga de la liga.
- **Registro de auditoría**: historial de acciones administrativas relevantes (quién hizo qué y cuándo), para trazabilidad.
- **Administración de datos**: herramientas internas de mantenimiento de datos, de uso ocasional por el equipo técnico.
## 5. Copias de seguridad
El sistema incluye un mecanismo de respaldo automático programado de la base de datos. Esta función es de uso interno (no requiere acción del usuario administrador) y se activa desde la configuración del servidor por el equipo técnico responsable del despliegue.
## 6. Resolución de problemas frecuentes
| Situación | Qué hacer |
|---|---|
| No puedo iniciar sesión | Verificar correo y contraseña; usar "Recuperar contraseña" si es necesario. Si la cuenta fue desactivada, contactar a un administrador. |
| No veo la opción para administrar cierta sección | El rol asignado a la cuenta no tiene permiso sobre esa sección; contactar a un administrador para revisar el rol. |
| Un partido no aparece en el fixture | Verificar que la etapa del torneo haya sido generada y que el equipo esté correctamente inscripto en la división. |
| Cargué un resultado y la tabla de posiciones no cambió | Revisar que el resultado se haya guardado correctamente (no quedó en estado pendiente); recargar la página. |
| No puedo cargar el resultado de un partido | Revisar que todos los jugadores cargados estén habilitados (ficha médica aprobada con archivo) y sin sanción activa; si el equipo tiene menos de 4 jugadores habilitados, hay que cargar el partido como walkover en lugar de una planilla común. |
| No puedo iniciar el torneo | Revisar que cada zona tenga al menos 2 equipos y que cada equipo inscripto tenga al menos 4 jugadores habilitados; el sistema indica qué falta corregir. |
| No encuentro el equipo de una temporada anterior en la pestaña Equipos | Esa pestaña lista clubes, no equipos por temporada — buscar el club (la institución) y entrar a **Ver** para encontrar el equipo de la temporada buscada en su historial. |
| No puedo eliminar un club | El club todavía tiene equipos o escuadras vinculadas; eliminar primero esos equipos desde el historial del club, o desvincular las escuadras, antes de poder eliminar el club. |
| No puedo guardar una zona con varios sub-grupos | Una zona con 2 o más sub-grupos necesita al menos una copa configurada, para poder determinar un campeón entre los sub-grupos; agregar una copa o reducir la zona a un solo grupo. |
## 7. Soporte técnico
Para consultas sobre instalación, configuración del servidor o errores técnicos, ver la documentación técnica en [README.md](./README.md) o contactar al equipo de desarrollo del proyecto.
File diff suppressed because it is too large Load Diff