Módulo para que agentes de soporte registren y consulten notas internas sobre jugadores.
Stack: Laravel 13 · Livewire 3 · spatie/laravel-permission · PHP 8.4 · MySQL 8.4 en Docker (SQLite para tests y modo nativo)
Solo se necesita Docker — la app corre con Dockerfile + docker-compose.yml (app + MySQL 8.4):
docker compose up --build -d # espera el healthcheck de MySQL, migra y seedea solo
# → http://localhost:8000
docker compose run --rm --no-deps --entrypoint "" app php artisan test # suite de tests
docker compose down # detener (agregar -v para borrar los datos de MySQL)Con make instalado hay atajos: make up, make test, make logs, make shell, make fresh, make down.
Alternativa con PHP 8.4 local (sin Docker, usa SQLite)
composer install
cp .env.example .env && php artisan key:generate
touch database/database.sqlite
php artisan migrate --seed
php artisan serve| Rol | Puede agregar notas | |
|---|---|---|
agent@example.com |
support-agent |
✅ (permiso player-notes.create) |
viewer@example.com |
— | ❌ solo lectura |
players/{player}/notes → PlayerNotesController → vista de página
├─ <livewire:player-notes-list> (Fecha · Autor · Nota)
└─ <livewire:create-player-note> (solo con @can)
│ save(): authorize → validate → repositorio
└─ dispatch('player-note-created') ─► #[On] en la lista → re-render
Componentes Livewire ──► PlayerNoteRepositoryInterface (abstracción)
└─ EloquentPlayerNoteRepository (binding en RepositoryServiceProvider)
- Patrón Repositorio con inversión de dependencias (SOLID/DIP): los componentes dependen de
PlayerNoteRepositoryInterface; la implementación Eloquent se resuelve vía binding enRepositoryServiceProvider. Cambiar de almacenamiento (u otro ORM) no toca la capa de presentación. - Dos componentes Livewire comunicados por evento (SRP): el formulario despacha
player-note-createdy la lista lo escucha con#[On]— la tabla se refresca sin recargar la página (equivalente moderno deemit/$listenersde Livewire 2). - Permisos en dos niveles:
@can('player-notes.create')controla la visibilidad del formulario, y$this->authorize(...)dentro desave()lo exige server-side (una UI oculta no es seguridad). No se agregó una Policy: spatie ya registra cada permiso en el Gate y no hay lógica por instancia que justifique esa indirección. PlayerNote::MAX_LENGTH(const tipada PHP 8.3+) como única fuente del límite de caracteres para la regla de validación, elmaxlengthdel textarea y el tamaño de la columna.- Integridad referencial: borrar un jugador elimina sus notas (
cascadeOnDelete); no se puede borrar un usuario con notas escritas (restrictOnDelete) para preservar el historial de auditoría. - Validación:
rules()+messages()en el componente — nota obligatoria, máx. 500 caracteres, mensajes en español.
Dockerfilemulti-stage: las dependencias se instalan en un stagecomposer:2y la app corre sobrephp:8.4-cli(+pdo_mysql) como usuario no-root, con imágenes públicas oficiales. El código va horneado en la imagen —docker compose upno depende de permisos del host.docker-compose.yml: servicioapp(puerto 8000) +mysql:8.4con healthcheck; la app arranca recién cuando MySQL responde consultas (depends_on: condition: service_healthy).- Entrypoint autosuficiente: en cada boot materializa las variables del contenedor en
.env(necesario porqueartisan servesolo propaga una lista fija de variables a sus workers), ejecutamigratecon reintentos y corre los seeders — que son idempotentes, así el boot es repetible sin duplicar datos. - Tests aislados por diseño:
tests/bootstrap.phpfuerza SQLite en memoria en todos los canales de entorno ($_SERVER/$_ENV/putenv), de modo que correr la suite dentro del contenedor jamás toca la base MySQL de demo.
| Capa | Archivos |
|---|---|
| Migraciones | database/migrations/*_create_players_table.php, *_create_player_notes_table.php |
| Modelos | app/Models/Player.php, app/Models/PlayerNote.php, app/Models/User.php |
| Repositorio | app/Repositories/Contracts/PlayerNoteRepositoryInterface.php, app/Repositories/EloquentPlayerNoteRepository.php, app/Providers/RepositoryServiceProvider.php |
| Livewire | app/Livewire/CreatePlayerNote.php, app/Livewire/PlayerNotesList.php + vistas en resources/views/livewire/ |
| HTTP | app/Http/Controllers/PlayerNotesController.php, PlayerController.php, Auth/LoginController.php, routes/web.php |
| Permisos/Demo | database/seeders/RolesAndPermissionsSeeder.php, DatabaseSeeder.php |
| Tests | tests/Feature/PlayerNotesTest.php, tests/bootstrap.php |
| Infraestructura | Dockerfile, docker-compose.yml, docker/entrypoint.sh, Makefile |
make test (o el docker compose run equivalente) — 10 tests / 29 aserciones sobre SQLite en
memoria. El módulo cubre:
- Usuario autorizado crea una nota → persiste en BD, resetea el campo y despacha el evento.
- Validación: nota vacía (
required) y nota que excede el máximo (max:500). - Usuario sin permiso: la acción
save()responde 403 y el formulario no se renderiza. - La página lista fecha, autor (nombre del User) y contenido de cada nota.
- La lista se refresca al recibir el evento
player-note-created. - Invitados son redirigidos al login.