Agrego archivos iniciales

This commit is contained in:
Laucha1312
2026-06-04 14:49:28 -03:00
parent 751a8c9a1b
commit dde4f014bb
68 changed files with 4971 additions and 0 deletions
@@ -0,0 +1,244 @@
# OnAPB Genius Agent — Spec de Diseño
**Fecha:** 2026-04-09
**Rama:** `feature/genius-agent`
**Stack:** Laravel 12, Prism PHP (prism-php/prism), Google Gemini 1.5 Flash, MySQL, Alpine.js
---
## 1. Objetivo
Implementar un agente de IA conversacional ("OnAPB Genius") integrado en onapb.com con dos modos de operación según el rol del usuario:
- **Público** (no logueado, aficionado, jugador): asistente de navegación y consultas usando el `MANUAL_USUARIO.md` como contexto RAG simplificado. Sin tools. Sin persistencia entre sesiones.
- **Admin** (SuperAdmin rol=1 / GeneralAdmin rol=2): automatización de tareas mediante function calling (Tools). Historial persistente en MySQL, auto-purgado a los 30 días.
---
## 2. Decisiones de Arquitectura
| Decisión | Elección | Razón |
|---|---|---|
| SDK de AI | `prism-php/prism` | Soporte probado de Gemini 1.5 Flash + tools. Más estable que `laravel/ai` (nuevo). |
| Modelo | `gemini-1.5-flash` | Velocidad (25s), costo bajo, function calling. |
| Streaming | No — JSON completo | Hosting compartido Hostinger con límites PHP desconocidos. Evita problemas de output buffer. |
| Memoria admin | MySQL (`agent_threads`) | Sin Redis. JSON column para mensajes. Purge automático 30 días. |
| Memoria público | Session PHP | Stateless entre sesiones. Simple, sin overhead de BD. |
| Tools | Solo si admin_logged_in | Validado en `GeniusAgentService`, no en Gemini. |
---
## 3. Estructura de Archivos
```
app/
├── AI/
│ ├── Tools/
│ │ ├── CrearPartidoTool.php
│ │ ├── CargarPuntajeTool.php
│ │ ├── RedactarNoticiaTool.php
│ │ ├── ListarEquiposTool.php
│ │ └── ListarEventosTool.php
│ └── Prompts/
│ ├── SystemPromptAdmin.php
│ └── SystemPromptPublic.php
├── Services/
│ └── GeniusAgentService.php
├── Http/Controllers/
│ └── GeniusAgentController.php
├── Models/
│ └── AgentThread.php
└── Console/Commands/
└── PurgeAgentThreads.php
database/migrations/
└── xxxx_create_agent_threads_table.php
resources/views/components/
└── genius-chat.blade.php
routes/web.php
└── POST /agent/chat (throttle: 20/min por IP)
```
---
## 4. Schema de Base de Datos
```sql
CREATE TABLE agent_threads (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
thread_id VARCHAR(36) NOT NULL UNIQUE, -- UUID generado en frontend
admin_id INT NOT NULL, -- session('admin_id')
messages JSON NOT NULL, -- array [{role, content}]
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL -- created_at + 30 días
);
```
---
## 5. Tools Disponibles (Admin)
Cada tool implementa la interfaz Prism `Tool`. El agent loop permite que Gemini llame la misma tool N veces para operaciones batch (ej. cargar puntajes de todos los partidos de una jornada).
| Tool | Parámetros | Acción en BD |
|---|---|---|
| `CrearPartidoTool` | `id_equipo_local`, `id_equipo_visitante`, `fecha_evento`, `hora_inicio`, `hora_fin`, `sede`, `id_torneo` | `Evento::create(...)` |
| `CargarPuntajeTool` | `id_evento`, `marcador_local`, `marcador_visitante` | `Evento::find()->update(...)` |
| `RedactarNoticiaTool` | `titulo`, `contenido`, `id_torneo?`, `categoria?` | `Noticia::create(...)` |
| `ListarEquiposTool` | `id_torneo?`, `id_club?`, `grupo?` | `Equipo::query()` con join a pivot `torneo_equipo` filtrando por `grupo` (solo lectura) |
| `ListarEventosTool` | `fecha_desde?`, `fecha_hasta?`, `id_torneo?` | `Evento::query()->get()` (solo lectura) |
**Nota sobre `grupo`:** No es un modelo separado. Es una columna en la tabla pivot `torneo_equipo` (relación `Torneo::equipos()->withPivot('grupo')`). `ListarEquiposTool` filtra con `wherePivot('grupo', $grupo)`.
**Agregar una nueva tool en el futuro:**
1. Crear `app/AI/Tools/NuevaTool.php`
2. Registrarla en `GeniusAgentService::getAdminTools()`
---
## 6. Flujo de Datos
### Usuario público
```
POST /agent/chat { message }
→ session()->get('agent_messages', [])
→ GeniusAgentService::chatPublic(message, history)
→ System prompt: navegación + MANUAL_USUARIO.md completo como contexto (el archivo es pequeño; si crece >50KB usar solo las primeras 200 líneas)
→ Prism → Gemini (sin tools)
→ Respuesta de texto
→ session()->put('agent_messages', [...])
→ return JSON { reply }
```
### Admin
```
POST /agent/chat { message, thread_id? }
→ AgentThread::findOrCreate(thread_id, admin_id)
→ GeniusAgentService::chatAdmin(message, thread)
→ System prompt: automatización de tareas OnAPB
→ Prism → Gemini con tools
→ [si Gemini llama tool] → Tool::handle() → resultado
→ [Gemini puede llamar N tools] → agent loop
→ Respuesta final de texto
→ thread->appendMessages([...])
→ thread->save()
→ return JSON { reply, thread_id }
```
---
## 7. Seguridad
- **Tools bloqueadas a no-admins:** `GeniusAgentService` valida `session('admin_logged_in')` antes de incluir tools.
- **Rate limiting:** `throttle:20,1` en la ruta `/agent/chat`.
- **Validación de input:** `message` requerido, string, máx. 1000 caracteres.
- **Aislamiento de threads:** cada thread valida `admin_id = session('admin_id')`. No hay acceso cruzado entre admins.
- **API key:** solo en `.env` (`GEMINI_API_KEY`). Nunca en código ni en BD.
---
## 8. Manejo de Errores
| Caso | Comportamiento |
|---|---|
| Gemini timeout / 5xx | `catch RequestException` → JSON `{ error: "El agente no responde, reintentá en un momento." }` |
| Tool falla (ej. evento no existe) | Tool retorna `{ error: "..." }` → Gemini lo incorpora en su respuesta |
| Thread expirado / no encontrado | Se crea un nuevo thread automáticamente |
| API key inválida / quota excedida | Log en Laravel + respuesta genérica (sin exponer detalles de la API) |
| Timeout de Hostinger | `set_time_limit(120)` al inicio del controller action |
---
## 9. Purge de Threads
Comando `php artisan agent:purge-threads` que elimina `agent_threads` donde `expires_at < NOW()`.
**Schedule:** Se registra en `routes/console.php` para ejecutarse diariamente. En Hostinger sin queue worker, se puede configurar como cron en hPanel:
```
# Reemplazar /home/TU_USUARIO/public_html con la ruta real de tu cuenta Hostinger
0 3 * * * cd /home/TU_USUARIO/public_html && php artisan agent:purge-threads >> /dev/null 2>&1
```
---
## 10. Frontend — Chat Bubble
Componente Blade `genius-chat.blade.php` incluido al final de `resources/views/layouts/app.blade.php`:
- Botón flotante (bottom-right) con ícono de chat
- Panel slide-up con historial de mensajes de la sesión actual
- Alpine.js para estado local (open/closed, messages, loading spinner)
- Tailwind para estilos (sin dependencias adicionales)
- Rol-aware: el frontend no diferencia, la diferencia la hace el backend
---
## 11. Comandos de Deploy
### Instalación inicial (local y producción)
```bash
# 1. Instalar Prism PHP
composer require prism-php/prism
# 2. Publicar config de Prism (opcional)
php artisan vendor:publish --provider="EchoLabs\Prism\PrismServiceProvider"
# 3. Agregar en .env
GEMINI_API_KEY=tu_api_key_aqui
# 4. Ejecutar migration de agent_threads
php artisan migrate
# 5. Registrar el comando de purge (se hace automáticamente con el código)
# Verificar que está en routes/console.php
# 6. Limpiar cachés después del deploy
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan route:clear
# 7. Re-cachear para producción
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
### Comandos de mantenimiento
```bash
# Purge manual de threads expirados
php artisan agent:purge-threads
# Ver logs del agente
php artisan pail --filter="GeniusAgent"
# Limpiar todos los threads (emergencia)
php artisan tinker --execute="App\Models\AgentThread::truncate();"
```
### Deploy en Hostinger (via SSH o File Manager)
```bash
# Subir archivos nuevos y correr:
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
---
## 12. Configuración `.env` requerida
```env
GEMINI_API_KEY=tu_api_key_de_google_ai_studio
# Opcional: ajustar timeout HTTP de Prism
PRISM_HTTP_TIMEOUT=30
```