#!/bin/bash
# ================================================================
# OKRFEDEF — Sprint 05: Módulo OKRs completo
# Constructor, árbol visual, scoring Doerr, validación IA
# Ejecutar desde: /home/evolucionamos/public_html/estrategia
# Comando: bash sprint_05_okrs.sh
# ================================================================
set -e
echo "========================================="
echo "  OKRFEDEF — Sprint 05: Módulo OKRs"
echo "========================================="

# ── 1. CONTROLADORES Y JOBS ──────────────────────────────────────
php artisan make:controller OkrController --resource
php artisan make:controller KeyResultController --resource
php artisan make:controller InitiativeController --resource
php artisan make:controller CheckInController
php artisan make:controller CycleController

php artisan make:job KrValidationJob
php artisan make:job OkrScoringJob

php artisan make:event KrProposed
php artisan make:event OwnerAssigned
php artisan make:event OkrScoreUpdated

echo ">>> Archivos base creados. Escribiendo lógica..."

# ── 2. OKR SERVICE ───────────────────────────────────────────────
cat > app/Services/OkrService.php << 'PHP'
<?php
namespace App\Services;

use App\Models\Cycle;
use App\Models\KeyResult;
use App\Models\Objective;
use App\Models\Project;

class OkrService
{
    /**
     * Scoring Doerr:
     * score_KR = (current - baseline) / (target - baseline)  → 0.0 a 1.0
     * Verde  >= 0.70 — Logrado
     * Amarillo 0.40–0.69 — En progreso
     * Rojo   < 0.40  — En riesgo
     * NOTA: 1.0 exacto puede indicar meta poco retadora (principio FACTS)
     */
    public function calculateKrScore(KeyResult $kr): float
    {
        if ($kr->target == $kr->baseline) return 0.0;
        $raw = ($kr->current - $kr->baseline) / ($kr->target - $kr->baseline);
        return round(min(max($raw, 0.0), 1.0), 4);
    }

    public function getTrafficLight(float $score): string
    {
        if ($score >= 0.70) return 'green';
        if ($score >= 0.40) return 'yellow';
        return 'red';
    }

    public function updateKrScore(KeyResult $kr): void
    {
        $kr->score        = $this->calculateKrScore($kr);
        $kr->traffic_light = $this->getTrafficLight($kr->score);
        $kr->save();
        $this->updateObjectiveScore($kr->objective);
    }

    public function updateObjectiveScore(Objective $obj): void
    {
        $krs = $obj->keyResults()->where('status', 'active')->get();
        if ($krs->isEmpty()) return;
        $obj->score = round($krs->avg('score'), 4);
        $obj->save();
    }

    public function getCycleScoreSummary(Cycle $cycle): array
    {
        $objectives = $cycle->objectives()->with('keyResults')->get();
        $total = $objectives->count();
        if ($total === 0) return ['score' => 0, 'green' => 0, 'yellow' => 0, 'red' => 0];

        $green  = $objectives->filter(fn($o) => $o->score >= 0.70)->count();
        $yellow = $objectives->filter(fn($o) => $o->score >= 0.40 && $o->score < 0.70)->count();
        $red    = $objectives->filter(fn($o) => $o->score < 0.40)->count();

        return [
            'score'  => round($objectives->avg('score'), 4),
            'green'  => $green,
            'yellow' => $yellow,
            'red'    => $red,
            'total'  => $total,
        ];
    }

    public function buildOkrTree(Cycle $cycle): array
    {
        $objectives = $cycle->objectives()
            ->with(['keyResults.owner:id,name', 'keyResults.initiatives', 'owner:id,name'])
            ->orderBy('order')
            ->get();

        return $objectives->map(fn($obj) => [
            'id'           => $obj->id,
            'title'        => $obj->title,
            'level'        => $obj->level,
            'area'         => $obj->area,
            'score'        => $obj->score,
            'traffic_light'=> $this->getTrafficLight($obj->score),
            'owner'        => $obj->owner?->name,
            'key_results'  => $obj->keyResults->map(fn($kr) => [
                'id'           => $kr->id,
                'title'        => $kr->title,
                'metric'       => $kr->metric,
                'baseline'     => $kr->baseline,
                'target'       => $kr->target,
                'current'      => $kr->current,
                'unit'         => $kr->unit,
                'score'        => $kr->score,
                'traffic_light'=> $kr->traffic_light,
                'owner'        => $kr->owner?->name,
                'owner_id'     => $kr->owner_id,
                'due_date'     => $kr->due_date?->format('Y-m-d'),
                'status'       => $kr->status,
                'initiatives_count' => $kr->initiatives->count(),
            ]),
        ])->toArray();
    }
}
PHP

# ── 3. OKR CONTROLLER ────────────────────────────────────────────
cat > app/Http/Controllers/OkrController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Events\KrProposed;
use App\Events\OwnerAssigned;
use App\Jobs\KrValidationJob;
use App\Models\Cycle;
use App\Models\KeyResult;
use App\Models\Objective;
use App\Models\Project;
use App\Models\User;
use App\Services\OkrService;
use Illuminate\Http\Request;
use Inertia\Inertia;

class OkrController extends Controller
{
    public function __construct(private OkrService $okr) {}

    /**
     * OKR Tree — vista principal del consultor y del equipo
     */
    public function tree(Project $project, Cycle $cycle)
    {
        $tree    = $this->okr->buildOkrTree($cycle);
        $summary = $this->okr->getCycleScoreSummary($cycle);
        $users   = User::all(['id', 'name', 'email']);

        return Inertia::render('Okr/Tree', [
            'project' => $project->load('organization'),
            'cycle'   => $cycle,
            'tree'    => $tree,
            'summary' => $summary,
            'users'   => $users,
        ]);
    }

    /**
     * Constructor en vivo (retiro)
     */
    public function builder(Project $project, Cycle $cycle)
    {
        return Inertia::render('Okr/Builder', [
            'project' => $project->load('organization'),
            'cycle'   => $cycle,
            'tree'    => $this->okr->buildOkrTree($cycle),
            'users'   => User::all(['id', 'name']),
        ]);
    }

    /**
     * Crear Objective
     */
    public function storeObjective(Request $request, Project $project, Cycle $cycle)
    {
        $request->validate([
            'title'    => 'required|string|max:300',
            'level'    => 'required|in:institutional,area',
            'area'     => 'nullable|string|max:100',
            'owner_id' => 'nullable|exists:users,id',
            'weight'   => 'nullable|numeric|min:0.1|max:10',
        ]);

        $objective = Objective::create([
            'cycle_id'    => $cycle->id,
            'title'       => $request->title,
            'level'       => $request->level,
            'area'        => $request->area,
            'owner_id'    => $request->owner_id,
            'weight'      => $request->weight ?? 1.0,
            'status'      => 'active',
            'order'       => Objective::where('cycle_id', $cycle->id)->count(),
        ]);

        return response()->json([
            'objective' => $objective->load('owner:id,name'),
            'message'   => 'Objetivo creado',
        ]);
    }

    /**
     * Crear Key Result
     */
    public function storeKeyResult(Request $request, Project $project, Cycle $cycle, Objective $objective)
    {
        $request->validate([
            'title'    => 'required|string|max:300',
            'metric'   => 'required|string|max:200',
            'baseline' => 'required|numeric',
            'target'   => 'required|numeric',
            'unit'     => 'nullable|string|max:50',
            'owner_id' => 'nullable|exists:users,id',
            'due_date' => 'nullable|date',
        ]);

        $kr = KeyResult::create([
            'objective_id' => $objective->id,
            'title'        => $request->title,
            'metric'       => $request->metric,
            'baseline'     => $request->baseline,
            'target'       => $request->target,
            'current'      => $request->baseline,
            'unit'         => $request->unit,
            'owner_id'     => $request->owner_id,
            'due_date'     => $request->due_date,
            'status'       => 'active',
            'order'        => KeyResult::where('objective_id', $objective->id)->count(),
        ]);

        // Score inicial
        $this->okr->updateKrScore($kr);

        // Broadcast para actualizar árbol en tiempo real
        broadcast(new KrProposed($project->id, $cycle->id, $kr->load('owner:id,name'), $objective->id));

        // Disparar validación IA
        KrValidationJob::dispatch($kr, $project);

        return response()->json([
            'kr'      => $kr->fresh()->load('owner:id,name'),
            'message' => 'Claude está validando el Key Result...',
        ]);
    }

    /**
     * Asignar owner a KR (drag & drop)
     */
    public function assignOwner(Request $request, Project $project, Cycle $cycle, KeyResult $kr)
    {
        $request->validate(['owner_id' => 'required|exists:users,id']);

        $kr->update(['owner_id' => $request->owner_id]);
        $owner = User::find($request->owner_id);

        broadcast(new OwnerAssigned($project->id, $kr->id, $owner->name));

        return response()->json(['kr' => $kr->fresh()->load('owner:id,name')]);
    }

    /**
     * Dashboard de seguimiento (post-retiro)
     */
    public function dashboard(Project $project, Cycle $cycle)
    {
        $tree    = $this->okr->buildOkrTree($cycle);
        $summary = $this->okr->getCycleScoreSummary($cycle);

        // KRs críticos (rojos y amarillos)
        $critical = KeyResult::whereHas('objective', fn($q) => $q->where('cycle_id', $cycle->id))
            ->where('traffic_light', 'red')
            ->with(['objective', 'owner:id,name'])
            ->get();

        return Inertia::render('Okr/Dashboard', [
            'project'  => $project->load('organization'),
            'cycle'    => $cycle,
            'tree'     => $tree,
            'summary'  => $summary,
            'critical' => $critical,
        ]);
    }

    /**
     * Vista Junta Directiva (solo nivel institucional)
     */
    public function boardView(Project $project, Cycle $cycle)
    {
        $tree = collect($this->okr->buildOkrTree($cycle))
            ->where('level', 'institutional')
            ->values();

        $summary = $this->okr->getCycleScoreSummary($cycle);

        return Inertia::render('Okr/BoardView', [
            'project' => $project->load('organization'),
            'cycle'   => $cycle,
            'tree'    => $tree,
            'summary' => $summary,
        ]);
    }
}
PHP

# ── 4. CHECKIN CONTROLLER ────────────────────────────────────────
cat > app/Http/Controllers/CheckInController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Events\OkrScoreUpdated;
use App\Models\CheckIn;
use App\Models\KeyResult;
use App\Models\Project;
use App\Models\Cycle;
use App\Services\OkrService;
use Illuminate\Http\Request;
use Inertia\Inertia;

class CheckInController extends Controller
{
    public function __construct(private OkrService $okr) {}

    /**
     * Vista de check-in para el owner (semanal)
     */
    public function index(Project $project, Cycle $cycle)
    {
        $myKrs = KeyResult::whereHas('objective', fn($q) => $q->where('cycle_id', $cycle->id))
            ->where('owner_id', auth()->id())
            ->where('status', 'active')
            ->with(['objective', 'checkIns' => fn($q) => $q->latest()->take(3)])
            ->get();

        return Inertia::render('Okr/CheckIn', [
            'project' => $project->load('organization'),
            'cycle'   => $cycle,
            'myKrs'   => $myKrs,
            'week'    => now()->weekOfYear,
            'year'    => now()->year,
        ]);
    }

    /**
     * Guardar check-in
     */
    public function store(Request $request, Project $project, Cycle $cycle)
    {
        $request->validate([
            'key_result_id' => 'required|exists:key_results,id',
            'value'         => 'required|numeric',
            'note'          => 'nullable|string|max:500',
            'confidence'    => 'required|in:low,medium,high',
        ]);

        $kr = KeyResult::findOrFail($request->key_result_id);

        // Guardar check-in
        $checkIn = CheckIn::create([
            'key_result_id'   => $kr->id,
            'user_id'         => auth()->id(),
            'value'           => $request->value,
            'note'            => $request->note,
            'week_number'     => now()->weekOfYear,
            'year'            => now()->year,
            'confidence'      => $request->confidence,
        ]);

        // Actualizar valor actual del KR y recalcular score
        $kr->current = $request->value;
        $kr->save();
        $this->okr->updateKrScore($kr);

        // Guardar score en el check-in
        $checkIn->update(['score_at_checkin' => $kr->fresh()->score]);

        // Broadcast actualización a todos
        broadcast(new OkrScoreUpdated($project->id, $cycle->id, $kr->fresh()));

        return response()->json([
            'checkin'    => $checkIn,
            'kr'         => $kr->fresh(),
            'new_score'  => $kr->fresh()->score,
            'new_light'  => $kr->fresh()->traffic_light,
        ]);
    }

    /**
     * Check-in masivo (múltiples KRs a la vez)
     */
    public function bulkStore(Request $request, Project $project, Cycle $cycle)
    {
        $request->validate([
            'checkins'                  => 'required|array',
            'checkins.*.key_result_id'  => 'required|exists:key_results,id',
            'checkins.*.value'          => 'required|numeric',
            'checkins.*.confidence'     => 'required|in:low,medium,high',
            'checkins.*.note'           => 'nullable|string|max:500',
        ]);

        $results = [];
        foreach ($request->checkins as $data) {
            $kr = KeyResult::findOrFail($data['key_result_id']);

            CheckIn::create([
                'key_result_id' => $kr->id,
                'user_id'       => auth()->id(),
                'value'         => $data['value'],
                'note'          => $data['note'] ?? null,
                'week_number'   => now()->weekOfYear,
                'year'          => now()->year,
                'confidence'    => $data['confidence'],
            ]);

            $kr->current = $data['value'];
            $kr->save();
            $this->okr->updateKrScore($kr);

            broadcast(new OkrScoreUpdated($project->id, $cycle->id, $kr->fresh()));
            $results[] = $kr->fresh();
        }

        return response()->json(['updated' => count($results), 'krs' => $results]);
    }
}
PHP

# ── 5. INITIATIVE CONTROLLER ─────────────────────────────────────
cat > app/Http/Controllers/InitiativeController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Models\Initiative;
use App\Models\KeyResult;
use App\Models\Project;
use App\Models\Cycle;
use Illuminate\Http\Request;
use Inertia\Inertia;

class InitiativeController extends Controller
{
    public function index(Project $project, Cycle $cycle)
    {
        $initiatives = Initiative::whereHas('keyResult.objective', fn($q) =>
            $q->where('cycle_id', $cycle->id))
            ->with(['keyResult.objective', 'owner:id,name'])
            ->get();

        return Inertia::render('Okr/Initiatives', [
            'project'     => $project->load('organization'),
            'cycle'       => $cycle,
            'initiatives' => $initiatives->groupBy('keyResult.objective.title'),
        ]);
    }

    public function store(Request $request, Project $project, Cycle $cycle, KeyResult $kr)
    {
        $request->validate([
            'title'       => 'required|string|max:300',
            'description' => 'nullable|string|max:500',
            'owner_id'    => 'nullable|exists:users,id',
            'start_date'  => 'nullable|date',
            'due_date'    => 'nullable|date',
            'dependencies'=> 'nullable|array',
        ]);

        $initiative = Initiative::create([
            'key_result_id' => $kr->id,
            'title'         => $request->title,
            'description'   => $request->description,
            'owner_id'      => $request->owner_id,
            'start_date'    => $request->start_date,
            'due_date'      => $request->due_date,
            'dependencies'  => $request->dependencies,
            'status'        => 'planned',
        ]);

        return response()->json(['initiative' => $initiative->load('owner:id,name')]);
    }

    public function update(Request $request, Project $project, Cycle $cycle, Initiative $initiative)
    {
        $request->validate([
            'status'   => 'sometimes|in:planned,active,done,cancelled',
            'progress' => 'sometimes|integer|min:0|max:100',
        ]);

        $initiative->update($request->only(['status', 'progress', 'title', 'owner_id', 'due_date']));

        return response()->json(['initiative' => $initiative->fresh()->load('owner:id,name')]);
    }
}
PHP

# ── 6. CYCLE CONTROLLER ──────────────────────────────────────────
cat > app/Http/Controllers/CycleController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Models\Cycle;
use App\Models\Project;
use Illuminate\Http\Request;

class CycleController extends Controller
{
    public function store(Request $request, Project $project)
    {
        $request->validate([
            'quarter'    => 'required|integer|min:1|max:4',
            'year'       => 'required|integer|min:2024|max:2030',
            'start_date' => 'required|date',
            'end_date'   => 'required|date|after:start_date',
        ]);

        $cycle = Cycle::create([
            'project_id' => $project->id,
            'quarter'    => $request->quarter,
            'year'       => $request->year,
            'start_date' => $request->start_date,
            'end_date'   => $request->end_date,
            'status'     => 'active',
        ]);

        return response()->json(['cycle' => $cycle]);
    }

    public function index(Project $project)
    {
        return response()->json([
            'cycles' => Cycle::where('project_id', $project->id)
                ->orderByDesc('year')->orderByDesc('quarter')->get(),
        ]);
    }
}
PHP

# ── 7. KR VALIDATION JOB (Claude) ────────────────────────────────
cat > app/Jobs/KrValidationJob.php << 'PHP'
<?php
namespace App\Jobs;

use App\Models\AiMessage;
use App\Models\KeyResult;
use App\Models\Project;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;

class KrValidationJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $timeout = 60;

    public function __construct(
        public KeyResult $kr,
        public Project   $project
    ) {}

    public function handle(): void
    {
        $objective = $this->kr->objective;

        $prompt = <<<PROMPT
Eres un experto en OKRs (metodología Doerr) evaluando un Key Result propuesto.

CONTEXTO:
- Organización: {$this->project->organization->name}
- Objetivo: "{$objective->title}"

KEY RESULT PROPUESTO:
- Título: "{$this->kr->title}"
- Métrica: {$this->kr->metric}
- Baseline: {$this->kr->baseline} {$this->kr->unit}
- Meta: {$this->kr->target} {$this->kr->unit}
- Fecha límite: {$this->kr->due_date}

Evalúa en exactamente este formato (máximo 120 palabras):

**¿ES MEDIBLE?** ✅/⚠️/❌ — [Una frase]
**¿ES UN RESULTADO?** ✅/⚠️/❌ — [Una frase. Si es una tarea/actividad, dilo]
**¿ES RETADOR?** ✅/⚠️/❌ — [Una frase. Un buen KR tiene 60-70% probabilidad de lograrse]
**¿TIENE DUEÑO CLARO?** ✅/⚠️/❌ — [Una frase]

**SUGERENCIA DE MEJORA:** [Si hay algo que mejorar, una frase concreta. Si está bien, escribe "Está bien formulado."]

**VEREDICTO:** APROBADO ✅ / MEJORAR ⚠️ / REFORMULAR ❌
PROMPT;

        $response = Http::withHeaders([
            'x-api-key'         => config('services.anthropic.api_key'),
            'anthropic-version' => '2023-06-01',
            'content-type'      => 'application/json',
        ])->timeout(60)->post('https://api.anthropic.com/v1/messages', [
            'model'      => config('services.anthropic.model', 'claude-sonnet-4-20250514'),
            'max_tokens' => 400,
            'messages'   => [['role' => 'user', 'content' => $prompt]],
        ]);

        if ($response->successful()) {
            $content = $response->json('content.0.text', '');
            $tokens  = $response->json('usage.input_tokens', 0) + $response->json('usage.output_tokens', 0);

            AiMessage::create([
                'project_id'   => $this->project->id,
                'user_id'      => $this->kr->owner_id,
                'module'       => 'kr_validation',
                'context'      => [
                    'kr_id'       => $this->kr->id,
                    'kr_title'    => $this->kr->title,
                    'objective'   => $objective->title,
                ],
                'prompt'       => $prompt,
                'response'     => $content,
                'model'        => config('services.anthropic.model'),
                'tokens_used'  => $tokens,
                'trigger'      => 'proactive',
            ]);

            // Broadcast validación al árbol en vivo
            broadcast(new \App\Events\KrProposed(
                $this->project->id,
                $objective->cycle_id,
                $this->kr->fresh(),
                $objective->id,
                $content
            ));
        }
    }
}
PHP

# ── 8. EVENTOS ───────────────────────────────────────────────────
cat > app/Events/KrProposed.php << 'PHP'
<?php
namespace App\Events;

use App\Models\KeyResult;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class KrProposed implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public int        $projectId,
        public int        $cycleId,
        public KeyResult  $kr,
        public int        $objectiveId,
        public ?string    $aiValidation = null
    ) {}

    public function broadcastOn(): Channel
    {
        return new Channel("project.{$this->projectId}.okr.{$this->cycleId}");
    }

    public function broadcastAs(): string { return 'kr.proposed'; }

    public function broadcastWith(): array
    {
        return [
            'objective_id'  => $this->objectiveId,
            'kr'            => [
                'id'           => $this->kr->id,
                'title'        => $this->kr->title,
                'metric'       => $this->kr->metric,
                'target'       => $this->kr->target,
                'score'        => $this->kr->score,
                'traffic_light'=> $this->kr->traffic_light,
                'owner'        => $this->kr->owner?->name,
            ],
            'ai_validation' => $this->aiValidation,
        ];
    }
}
PHP

cat > app/Events/OwnerAssigned.php << 'PHP'
<?php
namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OwnerAssigned implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public int    $projectId,
        public int    $krId,
        public string $ownerName
    ) {}

    public function broadcastOn(): Channel
    {
        return new Channel("project.{$this->projectId}.okr");
    }

    public function broadcastAs(): string { return 'owner.assigned'; }

    public function broadcastWith(): array
    {
        return ['kr_id' => $this->krId, 'owner_name' => $this->ownerName];
    }
}
PHP

cat > app/Events/OkrScoreUpdated.php << 'PHP'
<?php
namespace App\Events;

use App\Models\KeyResult;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OkrScoreUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public int       $projectId,
        public int       $cycleId,
        public KeyResult $kr
    ) {}

    public function broadcastOn(): Channel
    {
        return new Channel("project.{$this->projectId}.okr.{$this->cycleId}");
    }

    public function broadcastAs(): string { return 'score.updated'; }

    public function broadcastWith(): array
    {
        return [
            'kr_id'         => $this->kr->id,
            'objective_id'  => $this->kr->objective_id,
            'score'         => $this->kr->score,
            'traffic_light' => $this->kr->traffic_light,
            'current'       => $this->kr->current,
            'objective_score' => $this->kr->objective->score,
        ];
    }
}
PHP

# ── 9. RUTAS ─────────────────────────────────────────────────────
cat >> routes/web.php << 'PHP'

// ── OKR Routes ───────────────────────────────────────────────────
use App\Http\Controllers\OkrController;
use App\Http\Controllers\CheckInController;
use App\Http\Controllers\InitiativeController;
use App\Http\Controllers\CycleController;

Route::middleware(['auth', 'verified'])->prefix('projects/{project}')->group(function () {

    // Ciclos
    Route::get('/cycles',                     [CycleController::class, 'index'])->name('cycles.index');
    Route::post('/cycles',                    [CycleController::class, 'store'])->name('cycles.store');

    // OKR Tree y vistas
    Route::get('/cycles/{cycle}/okr',         [OkrController::class, 'tree'])->name('okr.tree');
    Route::get('/cycles/{cycle}/okr/builder', [OkrController::class, 'builder'])->name('okr.builder');
    Route::get('/cycles/{cycle}/okr/dashboard',[OkrController::class, 'dashboard'])->name('okr.dashboard');
    Route::get('/cycles/{cycle}/okr/board',   [OkrController::class, 'boardView'])->name('okr.board');

    // Objectives
    Route::post('/cycles/{cycle}/objectives', [OkrController::class, 'storeObjective'])->name('okr.objectives.store');

    // Key Results
    Route::post('/cycles/{cycle}/objectives/{objective}/kr', [OkrController::class, 'storeKeyResult'])->name('okr.kr.store');
    Route::post('/cycles/{cycle}/kr/{kr}/assign-owner',      [OkrController::class, 'assignOwner'])->name('okr.kr.assign-owner');

    // Check-ins
    Route::get('/cycles/{cycle}/checkin',     [CheckInController::class, 'index'])->name('okr.checkin');
    Route::post('/cycles/{cycle}/checkin',    [CheckInController::class, 'store'])->name('okr.checkin.store');
    Route::post('/cycles/{cycle}/checkin/bulk',[CheckInController::class, 'bulkStore'])->name('okr.checkin.bulk');

    // Initiatives
    Route::get('/cycles/{cycle}/initiatives',       [InitiativeController::class, 'index'])->name('okr.initiatives');
    Route::post('/cycles/{cycle}/kr/{kr}/initiatives', [InitiativeController::class, 'store'])->name('okr.initiatives.store');
    Route::patch('/cycles/{cycle}/initiatives/{initiative}', [InitiativeController::class, 'update'])->name('okr.initiatives.update');
});
PHP

# ── 10. VISTAS VUE ───────────────────────────────────────────────
mkdir -p resources/js/Pages/Okr

# Vista OKR Tree
cat > resources/js/Pages/Okr/Tree.vue << 'VUE'
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    cycle: Object,
    tree: Array,
    summary: Object,
    users: Array,
})

const tree = ref([...props.tree])
const summary = ref({...props.summary})

const trafficColors = {
    green:  'bg-green-100 border-green-400 text-green-800',
    yellow: 'bg-yellow-100 border-yellow-400 text-yellow-800',
    red:    'bg-red-100 border-red-400 text-red-800',
}
const scoreDot = (tl) => tl === 'green' ? '🟢' : tl === 'yellow' ? '🟡' : '🔴'

// WebSocket - actualizar scores en tiempo real
let channel = null
onMounted(() => {
    if (window.Echo) {
        channel = window.Echo.channel(`project.${props.project.id}.okr.${props.cycle.id}`)
            .listen('.score.updated', (data) => {
                tree.value.forEach(obj => {
                    if (obj.id === data.objective_id) {
                        obj.score = data.objective_score
                        obj.key_results.forEach(kr => {
                            if (kr.id === data.kr_id) {
                                kr.score        = data.score
                                kr.traffic_light = data.traffic_light
                                kr.current      = data.current
                            }
                        })
                    }
                })
            })
            .listen('.kr.proposed', (data) => {
                tree.value.forEach(obj => {
                    if (obj.id === data.objective_id) {
                        const exists = obj.key_results.find(k => k.id === data.kr.id)
                        if (!exists) obj.key_results.push(data.kr)
                    }
                })
            })
    }
})
onUnmounted(() => channel?.stopListening('.score.updated').stopListening('.kr.proposed'))

const scoreColor = (score) => {
    if (score >= 0.70) return 'text-green-600'
    if (score >= 0.40) return 'text-yellow-600'
    return 'text-red-600'
}
</script>

<template>
    <AppLayout :title="`OKR Tree — Q${cycle.quarter} ${cycle.year}`">
        <div class="max-w-5xl mx-auto px-4 py-6">

            <!-- Header con resumen -->
            <div class="flex justify-between items-start mb-6">
                <div>
                    <h1 class="text-2xl font-bold text-gray-900">OKR Tree</h1>
                    <p class="text-gray-500">{{ project.organization?.name }} · Q{{ cycle.quarter }} {{ cycle.year }}</p>
                </div>
                <div class="flex gap-3">
                    <a :href="route('okr.builder', [project.id, cycle.id])"
                       class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700">
                        + Constructor
                    </a>
                    <a :href="route('okr.dashboard', [project.id, cycle.id])"
                       class="bg-gray-800 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-700">
                        Dashboard →
                    </a>
                </div>
            </div>

            <!-- Resumen semáforo -->
            <div class="grid grid-cols-4 gap-3 mb-6">
                <div class="bg-white rounded-xl shadow p-4 text-center">
                    <div class="text-3xl font-black" :class="scoreColor(summary.score)">
                        {{ (summary.score * 100).toFixed(0) }}%
                    </div>
                    <div class="text-xs text-gray-500 mt-1">Score general</div>
                </div>
                <div class="bg-green-50 rounded-xl shadow p-4 text-center">
                    <div class="text-3xl font-black text-green-600">{{ summary.green }}</div>
                    <div class="text-xs text-green-600 mt-1">🟢 Verde</div>
                </div>
                <div class="bg-yellow-50 rounded-xl shadow p-4 text-center">
                    <div class="text-3xl font-black text-yellow-600">{{ summary.yellow }}</div>
                    <div class="text-xs text-yellow-600 mt-1">🟡 Amarillo</div>
                </div>
                <div class="bg-red-50 rounded-xl shadow p-4 text-center">
                    <div class="text-3xl font-black text-red-600">{{ summary.red }}</div>
                    <div class="text-xs text-red-600 mt-1">🔴 Rojo</div>
                </div>
            </div>

            <!-- OKR Tree -->
            <div class="space-y-4">
                <div v-for="obj in tree" :key="obj.id"
                     :class="['bg-white rounded-2xl shadow overflow-hidden border-l-4',
                         obj.score >= 0.70 ? 'border-green-500' : obj.score >= 0.40 ? 'border-yellow-500' : 'border-red-500']">

                    <!-- Objective header -->
                    <div class="p-4 bg-gray-50 flex justify-between items-center">
                        <div class="flex-1">
                            <div class="flex items-center gap-2">
                                <span class="text-xs font-bold uppercase px-2 py-0.5 rounded"
                                      :class="obj.level === 'institutional' ? 'bg-blue-100 text-blue-700' : 'bg-purple-100 text-purple-700'">
                                    {{ obj.level === 'institutional' ? 'Institucional' : obj.area }}
                                </span>
                                <h3 class="font-semibold text-gray-800">{{ obj.title }}</h3>
                            </div>
                            <p v-if="obj.owner" class="text-xs text-gray-500 mt-1">Owner: {{ obj.owner }}</p>
                        </div>
                        <div class="text-right">
                            <div class="text-2xl font-black" :class="scoreColor(obj.score)">
                                {{ scoreDot(obj.score >= 0.70 ? 'green' : obj.score >= 0.40 ? 'yellow' : 'red') }}
                                {{ (obj.score * 100).toFixed(0) }}%
                            </div>
                            <div class="text-xs text-gray-400">{{ obj.key_results?.length || 0 }} KRs</div>
                        </div>
                    </div>

                    <!-- Key Results -->
                    <div class="divide-y divide-gray-100">
                        <div v-for="kr in obj.key_results" :key="kr.id"
                             class="px-4 py-3 flex items-center gap-4">
                            <div class="w-2 h-2 rounded-full shrink-0"
                                 :class="kr.traffic_light === 'green' ? 'bg-green-500' : kr.traffic_light === 'yellow' ? 'bg-yellow-500' : 'bg-red-500'">
                            </div>
                            <div class="flex-1 min-w-0">
                                <p class="text-sm font-medium text-gray-800 truncate">{{ kr.title }}</p>
                                <p class="text-xs text-gray-500">
                                    {{ kr.metric }}: {{ kr.current }} / {{ kr.target }} {{ kr.unit }}
                                    <span v-if="kr.owner" class="ml-2">· {{ kr.owner }}</span>
                                </p>
                            </div>
                            <div class="text-right shrink-0">
                                <div class="text-sm font-bold" :class="scoreColor(kr.score)">
                                    {{ (kr.score * 100).toFixed(0) }}%
                                </div>
                            </div>
                        </div>

                        <div v-if="!obj.key_results?.length" class="px-4 py-3 text-sm text-gray-400 italic">
                            Sin Key Results aún
                        </div>
                    </div>
                </div>

                <div v-if="!tree.length" class="text-center py-12 text-gray-400">
                    <p class="text-lg">Sin objetivos creados</p>
                    <a :href="route('okr.builder', [project.id, cycle.id])"
                       class="mt-3 inline-block bg-blue-600 text-white px-4 py-2 rounded-lg text-sm">
                        Ir al constructor →
                    </a>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# Vista Builder (retiro en vivo)
cat > resources/js/Pages/Okr/Builder.vue << 'VUE'
<script setup>
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    cycle: Object,
    tree: Array,
    users: Array,
})

const tree = ref([...props.tree])
const showObjForm = ref(false)
const showKrForm = ref(null)

const objForm = ref({ title: '', level: 'institutional', area: '', owner_id: '' })
const krForm  = ref({ title: '', metric: '', baseline: 0, target: 0, unit: '', owner_id: '', due_date: '' })
const submitting = ref(false)

const addObjective = () => {
    submitting.value = true
    router.post(route('okr.objectives.store', [props.project.id, props.cycle.id]),
        objForm.value,
        {
            onSuccess: () => { showObjForm.value = false; objForm.value = { title:'', level:'institutional', area:'', owner_id:'' }; submitting.value = false },
            onError: () => { submitting.value = false },
            preserveState: false,
        }
    )
}

const addKeyResult = (objectiveId) => {
    submitting.value = true
    router.post(route('okr.kr.store', [props.project.id, props.cycle.id, objectiveId]),
        krForm.value,
        {
            onSuccess: () => { showKrForm.value = null; krForm.value = { title:'', metric:'', baseline:0, target:0, unit:'', owner_id:'', due_date:'' }; submitting.value = false },
            onError: () => { submitting.value = false },
            preserveState: false,
        }
    )
}

const scoreColor = (score) => score >= 0.70 ? 'text-green-600' : score >= 0.40 ? 'text-yellow-600' : 'text-red-600'
</script>

<template>
    <AppLayout :title="`Constructor OKRs — Q${cycle.quarter} ${cycle.year}`">
        <div class="max-w-4xl mx-auto px-4 py-6">

            <div class="flex justify-between items-center mb-6">
                <div>
                    <h1 class="text-2xl font-bold">Constructor OKRs</h1>
                    <p class="text-gray-500 text-sm">Q{{ cycle.quarter }} {{ cycle.year }} · {{ project.name }}</p>
                </div>
                <div class="flex gap-2">
                    <button @click="showObjForm = !showObjForm"
                            class="bg-blue-600 text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-blue-700">
                        + Nuevo Objetivo
                    </button>
                    <a :href="route('okr.tree', [project.id, cycle.id])"
                       class="bg-gray-200 text-gray-700 px-4 py-2 rounded-xl text-sm font-medium hover:bg-gray-300">
                        Ver árbol
                    </a>
                </div>
            </div>

            <!-- Formulario nuevo objetivo -->
            <div v-if="showObjForm" class="bg-white rounded-2xl shadow p-6 mb-6 border-2 border-blue-200">
                <h2 class="font-semibold mb-4">Nuevo Objetivo</h2>
                <div class="grid grid-cols-2 gap-3">
                    <div class="col-span-2">
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Título *</label>
                        <input v-model="objForm.title" placeholder="¿Qué quiere lograr la organización?"
                               class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-blue-500" />
                    </div>
                    <div>
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Nivel</label>
                        <select v-model="objForm.level" class="w-full border border-gray-300 rounded-lg p-2 text-sm">
                            <option value="institutional">Institucional</option>
                            <option value="area">Por área</option>
                        </select>
                    </div>
                    <div v-if="objForm.level === 'area'">
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Área</label>
                        <input v-model="objForm.area" placeholder="Ej: Crédito, Captación..."
                               class="w-full border border-gray-300 rounded-lg p-2 text-sm" />
                    </div>
                    <div>
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Owner</label>
                        <select v-model="objForm.owner_id" class="w-full border border-gray-300 rounded-lg p-2 text-sm">
                            <option value="">Sin asignar</option>
                            <option v-for="u in users" :key="u.id" :value="u.id">{{ u.name }}</option>
                        </select>
                    </div>
                    <div class="col-span-2 flex gap-2 pt-2">
                        <button @click="addObjective" :disabled="submitting || !objForm.title"
                                class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium disabled:bg-gray-300">
                            Crear objetivo
                        </button>
                        <button @click="showObjForm = false" class="text-gray-500 px-4 py-2 text-sm">Cancelar</button>
                    </div>
                </div>
            </div>

            <!-- Árbol de objetivos y KRs -->
            <div class="space-y-4">
                <div v-for="obj in tree" :key="obj.id" class="bg-white rounded-2xl shadow overflow-hidden">

                    <!-- Objective -->
                    <div class="bg-gray-800 text-white p-4 flex justify-between items-center">
                        <div>
                            <span class="text-xs opacity-60 uppercase">{{ obj.level === 'institutional' ? 'Institucional' : obj.area }}</span>
                            <h3 class="font-semibold">{{ obj.title }}</h3>
                            <span v-if="obj.owner" class="text-xs opacity-60">Owner: {{ obj.owner }}</span>
                        </div>
                        <button @click="showKrForm = showKrForm === obj.id ? null : obj.id"
                                class="bg-white bg-opacity-20 hover:bg-opacity-30 text-white text-sm px-3 py-1.5 rounded-lg">
                            + Key Result
                        </button>
                    </div>

                    <!-- Formulario KR -->
                    <div v-if="showKrForm === obj.id" class="p-4 bg-blue-50 border-b border-blue-200">
                        <h4 class="text-sm font-semibold mb-3 text-blue-800">Nuevo Key Result para "{{ obj.title }}"</h4>
                        <div class="grid grid-cols-2 gap-3">
                            <div class="col-span-2">
                                <input v-model="krForm.title" placeholder="¿Cómo medimos el éxito de este objetivo?"
                                       class="w-full border border-gray-300 rounded-lg p-2 text-sm" />
                            </div>
                            <input v-model="krForm.metric"   placeholder="Métrica (ej: Número de asociados)" class="border border-gray-300 rounded-lg p-2 text-sm" />
                            <input v-model="krForm.unit"     placeholder="Unidad (ej: asociados, COP, %)" class="border border-gray-300 rounded-lg p-2 text-sm" />
                            <input v-model.number="krForm.baseline" type="number" placeholder="Valor inicial (baseline)" class="border border-gray-300 rounded-lg p-2 text-sm" />
                            <input v-model.number="krForm.target"   type="number" placeholder="Meta (target)" class="border border-gray-300 rounded-lg p-2 text-sm" />
                            <select v-model="krForm.owner_id" class="border border-gray-300 rounded-lg p-2 text-sm">
                                <option value="">Asignar owner...</option>
                                <option v-for="u in users" :key="u.id" :value="u.id">{{ u.name }}</option>
                            </select>
                            <input v-model="krForm.due_date" type="date" class="border border-gray-300 rounded-lg p-2 text-sm" />
                            <div class="col-span-2 flex gap-2">
                                <button @click="addKeyResult(obj.id)" :disabled="submitting || !krForm.title || !krForm.metric"
                                        class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium disabled:bg-gray-300">
                                    Agregar KR + Validar con IA
                                </button>
                                <button @click="showKrForm = null" class="text-gray-500 text-sm px-3">Cancelar</button>
                            </div>
                        </div>
                    </div>

                    <!-- KRs existentes -->
                    <div class="divide-y divide-gray-100">
                        <div v-for="kr in obj.key_results" :key="kr.id" class="px-4 py-3 flex items-center gap-3">
                            <div class="w-2 h-2 rounded-full shrink-0"
                                 :class="kr.traffic_light === 'green' ? 'bg-green-500' : kr.traffic_light === 'yellow' ? 'bg-yellow-500' : 'bg-red-500'">
                            </div>
                            <div class="flex-1">
                                <p class="text-sm font-medium">{{ kr.title }}</p>
                                <p class="text-xs text-gray-400">{{ kr.metric }}: {{ kr.baseline }} → {{ kr.target }} {{ kr.unit }}
                                    <span v-if="kr.owner"> · {{ kr.owner }}</span>
                                </p>
                            </div>
                            <span class="text-sm font-bold" :class="scoreColor(kr.score)">
                                {{ (kr.score * 100).toFixed(0) }}%
                            </span>
                        </div>
                        <div v-if="!obj.key_results?.length" class="px-4 py-3 text-sm text-gray-400 italic text-center">
                            Sin Key Results — agrega el primero
                        </div>
                    </div>
                </div>

                <div v-if="!tree.length" class="text-center py-16 text-gray-400">
                    <p class="text-xl mb-2">Empieza creando el primer objetivo</p>
                    <p class="text-sm">Haz clic en "+ Nuevo Objetivo" para comenzar</p>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# Vista CheckIn (owner semanal)
cat > resources/js/Pages/Okr/CheckIn.vue << 'VUE'
<script setup>
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    cycle: Object,
    myKrs: Array,
    week: Number,
    year: Number,
})

const checkins = ref(
    props.myKrs.reduce((acc, kr) => {
        acc[kr.id] = { value: kr.current, confidence: 'medium', note: '' }
        return acc
    }, {})
)
const submitting = ref(false)
const submitted = ref(false)

const submit = () => {
    submitting.value = true
    const data = Object.entries(checkins.value).map(([krId, ci]) => ({
        key_result_id: parseInt(krId),
        value:         parseFloat(ci.value),
        confidence:    ci.confidence,
        note:          ci.note,
    }))

    router.post(route('okr.checkin.bulk', [props.project.id, props.cycle.id]),
        { checkins: data },
        {
            onSuccess: () => { submitted.value = true; submitting.value = false },
            onError: () => { submitting.value = false },
        }
    )
}

const trafficLight = (kr) => {
    if (!checkins.value[kr.id]) return kr.traffic_light
    const raw = (checkins.value[kr.id].value - kr.baseline) / (kr.target - kr.baseline)
    const score = Math.min(Math.max(raw, 0), 1)
    return score >= 0.70 ? 'green' : score >= 0.40 ? 'yellow' : 'red'
}

const confidenceLabels = { low: 'Baja confianza', medium: 'Confianza media', high: 'Alta confianza' }
const confidenceColors = { low: 'bg-red-100 text-red-700', medium: 'bg-yellow-100 text-yellow-700', high: 'bg-green-100 text-green-700' }
</script>

<template>
    <AppLayout :title="`Check-in Semana ${week} — ${project.name}`">
        <div class="max-w-2xl mx-auto px-4 py-8">

            <div class="mb-6">
                <h1 class="text-2xl font-bold text-gray-900">Check-in semanal</h1>
                <p class="text-gray-500 mt-1">Semana {{ week }} · {{ year }} · Q{{ cycle.quarter }}</p>
            </div>

            <div v-if="submitted" class="bg-green-50 border border-green-200 rounded-2xl p-8 text-center">
                <div class="text-5xl mb-4">✅</div>
                <h2 class="text-xl font-semibold text-green-800">Check-in registrado</h2>
                <p class="text-green-600 mt-2">Los scores se han actualizado automáticamente.</p>
                <a :href="route('okr.dashboard', [project.id, cycle.id])"
                   class="mt-4 inline-block bg-green-600 text-white px-4 py-2 rounded-xl text-sm font-medium">
                    Ver dashboard →
                </a>
            </div>

            <div v-else>
                <div v-if="myKrs.length === 0" class="text-center py-12 text-gray-400">
                    <p>No tienes Key Results asignados en este ciclo.</p>
                </div>

                <div v-else class="space-y-4">
                    <div v-for="kr in myKrs" :key="kr.id" class="bg-white rounded-2xl shadow p-5">
                        <div class="flex items-start justify-between mb-3">
                            <div class="flex-1">
                                <h3 class="font-medium text-gray-800">{{ kr.title }}</h3>
                                <p class="text-xs text-gray-500 mt-1">{{ kr.objective?.title }}</p>
                            </div>
                            <div class="w-3 h-3 rounded-full ml-3 mt-1 shrink-0"
                                 :class="trafficLight(kr) === 'green' ? 'bg-green-500' : trafficLight(kr) === 'yellow' ? 'bg-yellow-500' : 'bg-red-500'">
                            </div>
                        </div>

                        <!-- Valor actual -->
                        <div class="mb-3">
                            <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">
                                Valor actual ({{ kr.metric }})
                                <span class="text-gray-400 font-normal">Meta: {{ kr.target }} {{ kr.unit }}</span>
                            </label>
                            <input type="number" v-model.number="checkins[kr.id].value"
                                   :min="kr.baseline" :step="kr.target > 100 ? 1 : 0.1"
                                   class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-blue-500" />
                        </div>

                        <!-- Confianza -->
                        <div class="mb-3">
                            <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Nivel de confianza</label>
                            <div class="flex gap-2">
                                <button v-for="(label, key) in confidenceLabels" :key="key"
                                        @click="checkins[kr.id].confidence = key"
                                        :class="['flex-1 py-1.5 rounded-lg text-xs font-medium border transition-all',
                                            checkins[kr.id].confidence === key
                                                ? confidenceColors[key] + ' border-current'
                                                : 'border-gray-200 text-gray-500 hover:border-gray-400']">
                                    {{ label }}
                                </button>
                            </div>
                        </div>

                        <!-- Nota -->
                        <textarea v-model="checkins[kr.id].note" rows="2"
                                  placeholder="Nota opcional: ¿qué explica este avance?"
                                  class="w-full border border-gray-200 rounded-lg p-2 text-sm resize-none text-gray-600 placeholder-gray-400">
                        </textarea>

                        <!-- Historial reciente -->
                        <div v-if="kr.check_ins?.length" class="mt-2 flex gap-2">
                            <span v-for="ci in kr.check_ins" :key="ci.id"
                                  class="text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded">
                                {{ ci.value }} {{ kr.unit }}
                            </span>
                            <span class="text-xs text-gray-400">últimos check-ins</span>
                        </div>
                    </div>

                    <button @click="submit" :disabled="submitting"
                            class="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white font-bold py-4 rounded-2xl text-lg transition-colors shadow-lg">
                        {{ submitting ? 'Guardando...' : 'Registrar check-in' }}
                    </button>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# Vista Dashboard
cat > resources/js/Pages/Okr/Dashboard.vue << 'VUE'
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { Link } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    cycle: Object,
    tree: Array,
    summary: Object,
    critical: Array,
})

const summary = ref({...props.summary})
const tree = ref([...props.tree])

let channel = null
onMounted(() => {
    if (window.Echo) {
        channel = window.Echo.channel(`project.${props.project.id}.okr.${props.cycle.id}`)
            .listen('.score.updated', (data) => {
                tree.value.forEach(obj => {
                    if (obj.id === data.objective_id) {
                        obj.score = data.objective_score
                        obj.key_results.forEach(kr => {
                            if (kr.id === data.kr_id) {
                                kr.score = data.score
                                kr.traffic_light = data.traffic_light
                                kr.current = data.current
                            }
                        })
                    }
                })
                // Recalcular summary
                const allKrs = tree.value.flatMap(o => o.key_results)
                summary.value.green  = allKrs.filter(k => k.traffic_light === 'green').length
                summary.value.yellow = allKrs.filter(k => k.traffic_light === 'yellow').length
                summary.value.red    = allKrs.filter(k => k.traffic_light === 'red').length
            })
    }
})
onUnmounted(() => channel?.stopListening('.score.updated'))

const scoreBar = (score) => Math.round(score * 100)
const tlColor = (tl) => tl === 'green' ? 'bg-green-500' : tl === 'yellow' ? 'bg-yellow-500' : 'bg-red-500'
const scoreTextColor = (score) => score >= 0.70 ? 'text-green-600' : score >= 0.40 ? 'text-yellow-600' : 'text-red-600'
</script>

<template>
    <AppLayout :title="`Dashboard OKR — Q${cycle.quarter} ${cycle.year}`">
        <div class="max-w-5xl mx-auto px-4 py-6">

            <div class="flex justify-between items-center mb-6">
                <div>
                    <h1 class="text-2xl font-bold">Dashboard OKR</h1>
                    <p class="text-gray-500">{{ project.organization?.name }} · Q{{ cycle.quarter }} {{ cycle.year }}</p>
                </div>
                <a :href="route('okr.checkin', [project.id, cycle.id])"
                   class="bg-blue-600 text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-blue-700">
                    + Check-in semanal
                </a>
            </div>

            <!-- Score cards -->
            <div class="grid grid-cols-4 gap-4 mb-6">
                <div class="bg-white rounded-xl shadow p-4 text-center col-span-1">
                    <div class="text-4xl font-black" :class="scoreTextColor(summary.score)">
                        {{ scoreBar(summary.score) }}%
                    </div>
                    <div class="text-xs text-gray-500 mt-1">Score del plan</div>
                </div>
                <div class="bg-green-50 border border-green-200 rounded-xl shadow p-4 text-center">
                    <div class="text-4xl font-black text-green-600">{{ summary.green }}</div>
                    <div class="text-xs text-green-600 mt-1">En verde ≥ 70%</div>
                </div>
                <div class="bg-yellow-50 border border-yellow-200 rounded-xl shadow p-4 text-center">
                    <div class="text-4xl font-black text-yellow-600">{{ summary.yellow }}</div>
                    <div class="text-xs text-yellow-600 mt-1">Atención 40–69%</div>
                </div>
                <div class="bg-red-50 border border-red-200 rounded-xl shadow p-4 text-center">
                    <div class="text-4xl font-black text-red-600">{{ summary.red }}</div>
                    <div class="text-xs text-red-600 mt-1">En riesgo &lt; 40%</div>
                </div>
            </div>

            <!-- KRs críticos -->
            <div v-if="critical.length" class="bg-red-50 border border-red-200 rounded-2xl p-4 mb-6">
                <h2 class="text-sm font-semibold text-red-700 uppercase mb-3">🔴 Requieren atención inmediata</h2>
                <div class="space-y-2">
                    <div v-for="kr in critical" :key="kr.id" class="bg-white rounded-xl p-3 flex justify-between items-center">
                        <div>
                            <p class="text-sm font-medium text-gray-800">{{ kr.title }}</p>
                            <p class="text-xs text-gray-400">{{ kr.objective?.title }} · Owner: {{ kr.owner?.name || 'Sin asignar' }}</p>
                        </div>
                        <span class="text-red-600 font-bold text-sm">{{ scoreBar(kr.score) }}%</span>
                    </div>
                </div>
            </div>

            <!-- OKR Tree compacto -->
            <div class="space-y-3">
                <div v-for="obj in tree" :key="obj.id" class="bg-white rounded-2xl shadow overflow-hidden">
                    <div class="flex items-center gap-3 p-4 bg-gray-50">
                        <div class="w-1 self-stretch rounded-full"
                             :class="tlColor(obj.score >= 0.70 ? 'green' : obj.score >= 0.40 ? 'yellow' : 'red')">
                        </div>
                        <div class="flex-1">
                            <h3 class="font-semibold text-sm text-gray-800">{{ obj.title }}</h3>
                        </div>
                        <div class="text-right">
                            <div class="text-xl font-black" :class="scoreTextColor(obj.score)">
                                {{ scoreBar(obj.score) }}%
                            </div>
                            <div class="w-24 bg-gray-200 rounded-full h-1.5 mt-1">
                                <div :class="['h-1.5 rounded-full', tlColor(obj.score >= 0.70 ? 'green' : obj.score >= 0.40 ? 'yellow' : 'red')]"
                                     :style="`width: ${scoreBar(obj.score)}%`">
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="px-4 pb-2 pt-1 space-y-1.5">
                        <div v-for="kr in obj.key_results" :key="kr.id" class="flex items-center gap-2 py-1">
                            <div class="w-1.5 h-1.5 rounded-full shrink-0" :class="tlColor(kr.traffic_light)"></div>
                            <span class="text-xs text-gray-600 flex-1 truncate">{{ kr.title }}</span>
                            <span class="text-xs font-bold shrink-0" :class="scoreTextColor(kr.score)">
                                {{ kr.current }}/{{ kr.target }} {{ kr.unit }}
                            </span>
                            <span class="text-xs font-bold shrink-0" :class="scoreTextColor(kr.score)">
                                {{ scoreBar(kr.score) }}%
                            </span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# Vista Board (Junta Directiva)
cat > resources/js/Pages/Okr/BoardView.vue << 'VUE'
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    cycle: Object,
    tree: Array,
    summary: Object,
})

const scoreBar = (score) => Math.round(score * 100)
const scoreColor = (score) => score >= 0.70 ? 'text-green-600' : score >= 0.40 ? 'text-yellow-600' : 'text-red-600'
const tlBg = (tl) => tl === 'green' ? 'bg-green-500' : tl === 'yellow' ? 'bg-yellow-500' : 'bg-red-500'
</script>

<template>
    <AppLayout :title="`Vista Junta — ${project.name}`">
        <div class="max-w-4xl mx-auto px-4 py-8">

            <div class="mb-8 text-center">
                <h1 class="text-3xl font-bold text-gray-900">Plan Estratégico</h1>
                <p class="text-gray-500 mt-1">{{ project.organization?.name }} · Q{{ cycle.quarter }} {{ cycle.year }}</p>
                <div class="mt-4 inline-block">
                    <div class="text-5xl font-black" :class="scoreColor(summary.score)">
                        {{ scoreBar(summary.score) }}%
                    </div>
                    <div class="text-sm text-gray-400">avance general del plan</div>
                </div>
            </div>

            <div class="space-y-4">
                <div v-for="obj in tree" :key="obj.id"
                     :class="['rounded-2xl shadow p-6 border-l-4',
                         obj.score >= 0.70 ? 'border-green-500 bg-green-50' :
                         obj.score >= 0.40 ? 'border-yellow-500 bg-yellow-50' : 'border-red-500 bg-red-50']">
                    <div class="flex justify-between items-start mb-4">
                        <h2 class="text-lg font-bold text-gray-800 flex-1 pr-4">{{ obj.title }}</h2>
                        <div class="text-3xl font-black shrink-0" :class="scoreColor(obj.score)">
                            {{ scoreBar(obj.score) }}%
                        </div>
                    </div>
                    <div class="w-full bg-white bg-opacity-60 rounded-full h-3 mb-4">
                        <div :class="['h-3 rounded-full transition-all', tlBg(obj.score >= 0.70 ? 'green' : obj.score >= 0.40 ? 'yellow' : 'red')]"
                             :style="`width: ${scoreBar(obj.score)}%`">
                        </div>
                    </div>
                    <div class="text-sm text-gray-600">
                        {{ obj.key_results?.length || 0 }} indicadores clave ·
                        {{ obj.key_results?.filter(k => k.traffic_light === 'green').length || 0 }} en verde
                    </div>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# ── 11. COMPILAR Y CACHEAR ───────────────────────────────────────
echo ">>> Compilando assets Vue..."
npm run build

php artisan config:clear
php artisan route:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache

echo ""
echo "========================================="
echo "  Sprint 05 completado exitosamente"
echo ""
echo "  Módulos creados:"
echo "  ✅ OkrService (scoring Doerr completo)"
echo "  ✅ OkrController (tree, builder,"
echo "     dashboard, boardView)"
echo "  ✅ CheckInController (semanal + bulk)"
echo "  ✅ InitiativeController"
echo "  ✅ CycleController"
echo "  ✅ KrValidationJob (Claude valida KR)"
echo "  ✅ 3 eventos WebSocket (KrProposed,"
echo "     OwnerAssigned, OkrScoreUpdated)"
echo "  ✅ 5 vistas Vue: Tree, Builder,"
echo "     CheckIn, Dashboard, BoardView"
echo ""
echo "  Rutas disponibles:"
echo "  /projects/{id}/cycles/{c}/okr"
echo "  /projects/{id}/cycles/{c}/okr/builder"
echo "  /projects/{id}/cycles/{c}/okr/dashboard"
echo "  /projects/{id}/cycles/{c}/okr/board"
echo "  /projects/{id}/cycles/{c}/checkin"
echo "========================================="
