#!/bin/bash
# ================================================================
# OKRFEDEF — Sprint 04: Estrategia Emergente
# Apuestas estratégicas, renuncias, capacidades, aspiración
# Ejecutar desde: /home/evolucionamos/public_html/estrategia
# Comando: bash sprint_04_estrategia.sh
# ================================================================
set -e
echo "========================================="
echo "  OKRFEDEF — Sprint 04: Estrategia"
echo "========================================="

# ── 1. CONTROLADORES ─────────────────────────────────────────────
php artisan make:controller StrategicBetController --resource
php artisan make:controller RenunciationController
php artisan make:controller CapabilityController
php artisan make:controller AspirationController

# ── 2. JOBS Y EVENTOS ────────────────────────────────────────────
php artisan make:job BetValidationJob
php artisan make:job AspirationSynthesisJob
php artisan make:job CapabilityAnalysisJob
php artisan make:event BetProposed
php artisan make:event VoteSubmitted
php artisan make:event StrategicBlockCompleted

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

# ── 3. MIGRACIÓN PARA RENUNCIAS Y CAPACIDADES ────────────────────
cat > database/migrations/2026_03_24_100000_create_renunciations_table.php << 'MIGRATION'
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void {
        Schema::create('renunciations', function (Blueprint $table) {
            $table->id();
            $table->foreignId('project_id')->constrained('projects')->onDelete('cascade');
            $table->text('description');
            $table->text('justification')->nullable();
            $table->integer('votes_yes')->default(0);
            $table->integer('votes_no')->default(0);
            $table->integer('total_voters')->default(0);
            $table->decimal('approval_rate', 5, 2)->default(0);
            $table->boolean('formalized')->default(false);
            $table->decimal('threshold', 5, 2)->default(70.00);
            $table->foreignId('proposed_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamps();
        });

        Schema::create('renunciation_votes', function (Blueprint $table) {
            $table->id();
            $table->foreignId('renunciation_id')->constrained()->onDelete('cascade');
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->boolean('vote');
            $table->timestamps();
            $table->unique(['renunciation_id', 'user_id']);
        });

        Schema::create('capabilities', function (Blueprint $table) {
            $table->id();
            $table->foreignId('project_id')->constrained('projects')->onDelete('cascade');
            $table->string('title');
            $table->text('description')->nullable();
            $table->integer('votes')->default(0);
            $table->decimal('vote_percentage', 5, 2)->default(0);
            $table->boolean('is_custom')->default(false);
            $table->foreignId('proposed_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamps();
        });

        Schema::create('capability_votes', function (Blueprint $table) {
            $table->id();
            $table->foreignId('capability_id')->constrained()->onDelete('cascade');
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->timestamps();
            $table->unique(['capability_id', 'user_id']);
        });

        Schema::create('aspirations', function (Blueprint $table) {
            $table->id();
            $table->foreignId('project_id')->constrained('projects')->onDelete('cascade');
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->text('content');
            $table->string('theme')->nullable();
            $table->timestamps();
        });

        Schema::create('bet_votes', function (Blueprint $table) {
            $table->id();
            $table->foreignId('strategic_bet_id')->constrained()->onDelete('cascade');
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->tinyInteger('feasibility')->unsigned();
            $table->tinyInteger('impact')->unsigned();
            $table->timestamps();
            $table->unique(['strategic_bet_id', 'user_id']);
        });
    }

    public function down(): void {
        Schema::dropIfExists('bet_votes');
        Schema::dropIfExists('aspirations');
        Schema::dropIfExists('capability_votes');
        Schema::dropIfExists('capabilities');
        Schema::dropIfExists('renunciation_votes');
        Schema::dropIfExists('renunciations');
    }
};
MIGRATION

php artisan migrate --force

# ── 4. MODELOS ───────────────────────────────────────────────────
cat > app/Models/Renunciation.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Renunciation extends Model {
    use HasFactory;
    protected $fillable = ['project_id','description','justification','votes_yes','votes_no','total_voters','approval_rate','formalized','threshold','proposed_by'];
    protected $casts = ['formalized' => 'boolean'];
    public function project() { return $this->belongsTo(Project::class); }
    public function proposedBy() { return $this->belongsTo(User::class, 'proposed_by'); }
    public function votes() { return $this->hasMany(RenunciationVote::class); }
    public function recalculate(): void {
        $this->votes_yes = $this->votes()->where('vote', true)->count();
        $this->votes_no  = $this->votes()->where('vote', false)->count();
        $this->total_voters = $this->votes()->count();
        $this->approval_rate = $this->total_voters > 0
            ? round(($this->votes_yes / $this->total_voters) * 100, 2)
            : 0;
        $this->formalized = $this->approval_rate >= $this->threshold;
        $this->save();
    }
}
MODEL

cat > app/Models/RenunciationVote.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class RenunciationVote extends Model {
    protected $fillable = ['renunciation_id','user_id','vote'];
    protected $casts = ['vote' => 'boolean'];
    public function renunciation() { return $this->belongsTo(Renunciation::class); }
    public function user() { return $this->belongsTo(User::class); }
}
MODEL

cat > app/Models/Capability.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Capability extends Model {
    use HasFactory;
    protected $fillable = ['project_id','title','description','votes','vote_percentage','is_custom','proposed_by'];
    protected $casts = ['is_custom' => 'boolean'];
    public function project() { return $this->belongsTo(Project::class); }
    public function capabilityVotes() { return $this->hasMany(CapabilityVote::class); }
    public function recalculate(int $totalParticipants): void {
        $this->votes = $this->capabilityVotes()->count();
        $this->vote_percentage = $totalParticipants > 0
            ? round(($this->votes / $totalParticipants) * 100, 2)
            : 0;
        $this->save();
    }
}
MODEL

cat > app/Models/CapabilityVote.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class CapabilityVote extends Model {
    protected $fillable = ['capability_id','user_id'];
    public function capability() { return $this->belongsTo(Capability::class); }
    public function user() { return $this->belongsTo(User::class); }
}
MODEL

cat > app/Models/Aspiration.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Aspiration extends Model {
    use HasFactory;
    protected $fillable = ['project_id','user_id','content','theme'];
    public function project() { return $this->belongsTo(Project::class); }
    public function user() { return $this->belongsTo(User::class); }
}
MODEL

cat > app/Models/BetVote.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class BetVote extends Model {
    protected $fillable = ['strategic_bet_id','user_id','feasibility','impact'];
    public function strategicBet() { return $this->belongsTo(StrategicBet::class); }
    public function user() { return $this->belongsTo(User::class); }
}
MODEL

# Actualizar StrategicBet con relación a votos
cat > app/Models/StrategicBet.php << 'MODEL'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class StrategicBet extends Model {
    use HasFactory;
    protected $fillable = ['project_id','hypothesis','capability','renunciation','value_logic','risks','feasibility_avg','impact_avg','score','ai_validation','status','proposed_by'];
    public function project() { return $this->belongsTo(Project::class); }
    public function proposedBy() { return $this->belongsTo(User::class, 'proposed_by'); }
    public function betVotes() { return $this->hasMany(BetVote::class); }
    public function recalculateFromVotes(): void {
        $votes = $this->betVotes()->get();
        if ($votes->isEmpty()) return;
        $this->feasibility_avg = round($votes->avg('feasibility'), 2);
        $this->impact_avg      = round($votes->avg('impact'), 2);
        $this->score = round(($this->feasibility_avg * 0.4) + ($this->impact_avg * 0.6), 2);
        $this->save();
    }
}
MODEL

# ── 5. STRATEGIC SERVICE ─────────────────────────────────────────
cat > app/Services/StrategicService.php << 'PHP'
<?php
namespace App\Services;

use App\Models\Aspiration;
use App\Models\Capability;
use App\Models\Project;
use App\Models\Renunciation;
use App\Models\StrategicBet;

class StrategicService
{
    public function getProjectSummary(Project $project): array
    {
        return [
            'aspirations'    => Aspiration::where('project_id', $project->id)->get(),
            'capabilities'   => Capability::where('project_id', $project->id)->orderByDesc('votes')->get(),
            'renunciations'  => Renunciation::where('project_id', $project->id)->get(),
            'bets'           => StrategicBet::where('project_id', $project->id)
                                    ->orderByDesc('score')->get(),
            'top_capability' => Capability::where('project_id', $project->id)
                                    ->orderByDesc('votes')->first(),
            'formalized_renunciations' => Renunciation::where('project_id', $project->id)
                                    ->where('formalized', true)->count(),
            'active_bets'    => StrategicBet::where('project_id', $project->id)
                                    ->where('status', 'active')->count(),
        ];
    }

    public function buildContextForClaude(Project $project): string
    {
        $dofa = $project->dofaSessions()->where('status','completed')->latest()->first();
        $bets = StrategicBet::where('project_id', $project->id)->get();
        $caps = Capability::where('project_id', $project->id)->orderByDesc('votes')->take(3)->get();
        $rens = Renunciation::where('project_id', $project->id)->where('formalized', true)->get();

        $context  = "Organización: {$project->organization->name}\n";
        $context .= "Proyecto: {$project->name}\n\n";

        if ($dofa) {
            $context .= "DOFA — Cuadrante: {$dofa->quadrant} | MEFI: {$dofa->mefi_total} | MEFE: {$dofa->mefe_total}\n\n";
        }

        if ($caps->isNotEmpty()) {
            $context .= "CAPACIDADES DISTINTIVAS TOP:\n";
            foreach ($caps as $c) {
                $context .= "- {$c->title} ({$c->votes} votos)\n";
            }
            $context .= "\n";
        }

        if ($bets->isNotEmpty()) {
            $context .= "APUESTAS ESTRATÉGICAS:\n";
            foreach ($bets as $b) {
                $context .= "- [{$b->status}] {$b->hypothesis} (score: {$b->score})\n";
            }
            $context .= "\n";
        }

        if ($rens->isNotEmpty()) {
            $context .= "RENUNCIAS FORMALIZADAS:\n";
            foreach ($rens as $r) {
                $context .= "- {$r->description} ({$r->approval_rate}% aprobación)\n";
            }
        }

        return $context;
    }
}
PHP

# ── 6. JOBS ──────────────────────────────────────────────────────
cat > app/Jobs/BetValidationJob.php << 'PHP'
<?php
namespace App\Jobs;

use App\Models\AiMessage;
use App\Models\StrategicBet;
use App\Services\StrategicService;
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 BetValidationJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

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

    public function __construct(public StrategicBet $bet) {}

    public function handle(StrategicService $strategic): void
    {
        $project = $this->bet->project->load('organization');
        $context = $strategic->buildContextForClaude($project);

        $prompt = <<<PROMPT
Eres un consultor estratégico experto en el sector solidario colombiano.

CONTEXTO DEL PROYECTO:
{$context}

APUESTA ESTRATÉGICA PROPUESTA:
"{$this->bet->hypothesis}"

Capacidad relacionada: {$this->bet->capability}
Lógica de valor: {$this->bet->value_logic}

Evalúa esta apuesta estratégica respondiendo EXACTAMENTE en este formato:

**COHERENCIA CON EL DOFA** ✅/⚠️/❌
[Una oración sobre si la apuesta es coherente con el posicionamiento estratégico del DOFA]

**SUPUESTOS CRÍTICOS**
- [Supuesto 1 que debe ser verdad para que esta apuesta funcione]
- [Supuesto 2]
- [Supuesto 3]

**RIESGOS PRINCIPALES**
- [Riesgo 1 con su probabilidad: alta/media/baja]
- [Riesgo 2]

**PREGUNTA PROVOCADORA**
[La pregunta más incómoda que el equipo debe responder antes de comprometerse con esta apuesta]

**VEREDICTO**: [APOSTAR / REFINAR / DESCARTAR] — [Una oración de justificación]

Sé directo y específico para el contexto de FEDEF. Máximo 200 palabras.
PROMPT;

        $response = Http::withHeaders([
            'x-api-key'         => config('services.anthropic.api_key'),
            'anthropic-version' => '2023-06-01',
            'content-type'      => 'application/json',
        ])->timeout(90)->post('https://api.anthropic.com/v1/messages', [
            'model'      => config('services.anthropic.model', 'claude-sonnet-4-20250514'),
            'max_tokens' => 800,
            '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);

            $this->bet->ai_validation = $content;
            $this->bet->save();

            AiMessage::create([
                'project_id'  => $project->id,
                'module'      => 'bet',
                'context'     => ['bet_id' => $this->bet->id, 'hypothesis' => $this->bet->hypothesis],
                'prompt'      => $prompt,
                'response'    => $content,
                'model'       => config('services.anthropic.model'),
                'tokens_used' => $tokens,
                'trigger'     => 'proactive',
            ]);

            broadcast(new \App\Events\BetProposed($this->bet->fresh()));
        }
    }
}
PHP

cat > app/Jobs/AspirationSynthesisJob.php << 'PHP'
<?php
namespace App\Jobs;

use App\Models\AiMessage;
use App\Models\Aspiration;
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 AspirationSynthesisJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

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

    public function __construct(public Project $project) {}

    public function handle(): void
    {
        $aspirations = Aspiration::where('project_id', $this->project->id)->get();
        if ($aspirations->isEmpty()) return;

        $list = $aspirations->map(fn($a, $i) => ($i+1).". \"{$a->content}\"")->join("\n");

        $prompt = <<<PROMPT
Eres un consultor estratégico analizando las aspiraciones del equipo directivo de {$this->project->organization->name}.

ASPIRACIONES RECOGIDAS (respuesta a "FEDEF gana cuando..."):
{$list}

Analiza estas aspiraciones y responde en este formato:

**TEMAS CONVERGENTES** (lo que todos o la mayoría comparten)
- [Tema 1]: [Descripción breve] — [N participantes lo mencionaron]
- [Tema 2]: ...

**TENSIONES DETECTADAS** (donde hay visiones diferentes o contradictorias)
- [Tensión 1]: [Un grupo dice X, otro dice Y]
- [Tensión 2]: ...

**ASPIRACIÓN GANADORA PROPUESTA**
"[Redacción de una aspiración ganadora unificada de máximo 2 líneas que capture la esencia de lo que el equipo quiere lograr]"

**PREGUNTA PARA EL DEBATE**
[La pregunta que debe resolver el equipo para alinear las diferencias detectadas]

Sé preciso y usa el lenguaje de los participantes. Máximo 250 palabras.
PROMPT;

        $response = Http::withHeaders([
            'x-api-key'         => config('services.anthropic.api_key'),
            'anthropic-version' => '2023-06-01',
            'content-type'      => 'application/json',
        ])->timeout(90)->post('https://api.anthropic.com/v1/messages', [
            'model'      => config('services.anthropic.model', 'claude-sonnet-4-20250514'),
            'max_tokens' => 800,
            '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,
                'module'      => 'synthesis',
                'context'     => ['type' => 'aspiration', 'count' => $aspirations->count()],
                'prompt'      => $prompt,
                'response'    => $content,
                'model'       => config('services.anthropic.model'),
                'tokens_used' => $tokens,
                'trigger'     => 'proactive',
            ]);

            broadcast(new \App\Events\StrategicBlockCompleted($this->project->id, 'aspiration', $content));
        }
    }
}
PHP

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

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

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

    public function __construct(public StrategicBet $bet) {}

    public function broadcastOn(): Channel
    {
        return new Channel("project.{$this->bet->project_id}.strategy");
    }

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

    public function broadcastWith(): array
    {
        return [
            'bet_id'        => $this->bet->id,
            'hypothesis'    => $this->bet->hypothesis,
            'score'         => $this->bet->score,
            'ai_validation' => $this->bet->ai_validation,
            'status'        => $this->bet->status,
        ];
    }
}
PHP

cat > app/Events/VoteSubmitted.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 VoteSubmitted implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public int    $projectId,
        public string $type,
        public int    $entityId,
        public array  $result
    ) {}

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

    public function broadcastAs(): string { return 'vote.submitted'; }

    public function broadcastWith(): array
    {
        return [
            'type'      => $this->type,
            'entity_id' => $this->entityId,
            'result'    => $this->result,
        ];
    }
}
PHP

cat > app/Events/StrategicBlockCompleted.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 StrategicBlockCompleted implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public int    $projectId,
        public string $block,
        public string $aiAnalysis
    ) {}

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

    public function broadcastAs(): string { return 'block.completed'; }

    public function broadcastWith(): array
    {
        return [
            'block'       => $this->block,
            'ai_analysis' => $this->aiAnalysis,
        ];
    }
}
PHP

# ── 8. STRATEGIC BET CONTROLLER ──────────────────────────────────
cat > app/Http/Controllers/StrategicBetController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Models\Project;
use App\Models\StrategicBet;
use App\Models\BetVote;
use App\Jobs\BetValidationJob;
use App\Events\VoteSubmitted;
use Illuminate\Http\Request;
use Inertia\Inertia;

class StrategicBetController extends Controller
{
    public function index(Project $project)
    {
        return Inertia::render('Strategy/Bets', [
            'project' => $project->load('organization'),
            'bets'    => StrategicBet::where('project_id', $project->id)
                            ->with('proposedBy')
                            ->orderByDesc('score')->get(),
        ]);
    }

    public function store(Request $request, Project $project)
    {
        $request->validate([
            'hypothesis'  => 'required|string|max:500',
            'capability'  => 'nullable|string|max:200',
            'value_logic' => 'nullable|string|max:500',
        ]);

        $bet = StrategicBet::create([
            'project_id'  => $project->id,
            'hypothesis'  => $request->hypothesis,
            'capability'  => $request->capability,
            'value_logic' => $request->value_logic,
            'status'      => 'proposed',
            'proposed_by' => auth()->id(),
        ]);

        // Disparar validación de IA inmediatamente
        BetValidationJob::dispatch($bet);

        return response()->json(['bet' => $bet, 'message' => 'Claude está analizando la apuesta...']);
    }

    public function vote(Request $request, Project $project, StrategicBet $bet)
    {
        $request->validate([
            'feasibility' => 'required|integer|min:1|max:5',
            'impact'      => 'required|integer|min:1|max:5',
        ]);

        BetVote::updateOrCreate(
            ['strategic_bet_id' => $bet->id, 'user_id' => auth()->id()],
            ['feasibility' => $request->feasibility, 'impact' => $request->impact]
        );

        $bet->recalculateFromVotes();

        broadcast(new VoteSubmitted($project->id, 'bet', $bet->id, [
            'feasibility_avg' => $bet->feasibility_avg,
            'impact_avg'      => $bet->impact_avg,
            'score'           => $bet->score,
            'votes_count'     => $bet->betVotes()->count(),
        ]))->toOthers();

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

    public function activate(Project $project, StrategicBet $bet)
    {
        $bet->update(['status' => 'active']);
        return response()->json(['bet' => $bet]);
    }

    public function discard(Project $project, StrategicBet $bet)
    {
        $bet->update(['status' => 'discarded']);
        return response()->json(['bet' => $bet]);
    }
}
PHP

# ── 9. RENUNCIATION CONTROLLER ───────────────────────────────────
cat > app/Http/Controllers/RenunciationController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Models\Project;
use App\Models\Renunciation;
use App\Models\RenunciationVote;
use App\Events\VoteSubmitted;
use Illuminate\Http\Request;
use Inertia\Inertia;

class RenunciationController extends Controller
{
    public function index(Project $project)
    {
        return Inertia::render('Strategy/Renunciations', [
            'project'       => $project->load('organization'),
            'renunciations' => Renunciation::where('project_id', $project->id)
                                ->with('proposedBy')
                                ->orderByDesc('approval_rate')->get(),
        ]);
    }

    public function store(Request $request, Project $project)
    {
        $request->validate([
            'description'   => 'required|string|max:500',
            'justification' => 'nullable|string|max:500',
            'threshold'     => 'nullable|numeric|min:50|max:100',
        ]);

        $renunciation = Renunciation::create([
            'project_id'    => $project->id,
            'description'   => $request->description,
            'justification' => $request->justification,
            'threshold'     => $request->threshold ?? 70,
            'proposed_by'   => auth()->id(),
        ]);

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

    public function vote(Request $request, Project $project, Renunciation $renunciation)
    {
        $request->validate(['vote' => 'required|boolean']);

        RenunciationVote::updateOrCreate(
            ['renunciation_id' => $renunciation->id, 'user_id' => auth()->id()],
            ['vote' => $request->vote]
        );

        $renunciation->recalculate();

        broadcast(new VoteSubmitted($project->id, 'renunciation', $renunciation->id, [
            'votes_yes'     => $renunciation->votes_yes,
            'votes_no'      => $renunciation->votes_no,
            'approval_rate' => $renunciation->approval_rate,
            'formalized'    => $renunciation->formalized,
        ]))->toOthers();

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

    public function myVote(Project $project, Renunciation $renunciation)
    {
        $vote = RenunciationVote::where('renunciation_id', $renunciation->id)
            ->where('user_id', auth()->id())->first();
        return response()->json(['vote' => $vote?->vote]);
    }
}
PHP

# ── 10. CAPABILITY CONTROLLER ────────────────────────────────────
cat > app/Http/Controllers/CapabilityController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Models\Capability;
use App\Models\CapabilityVote;
use App\Models\Project;
use App\Events\VoteSubmitted;
use Illuminate\Http\Request;
use Inertia\Inertia;

class CapabilityController extends Controller
{
    // Capacidades predefinidas para FEDEF (editables)
    private array $defaults = [
        'Solidez financiera y cultura de pago de los asociados',
        'Atención personalizada y cercanía con el asociado',
        'Excelente reputación y confianza en el sector',
        'Agilidad operativa frente a entidades de mayor tamaño',
        'Cumplimiento normativo y gestión de riesgos sólida',
        'Red de convenios y alianzas con empresas empleadoras',
        'Flexibilidad para crear productos a medida del asociado',
        'Conocimiento profundo de las necesidades del segmento',
    ];

    public function index(Project $project)
    {
        $capabilities = Capability::where('project_id', $project->id)
            ->orderByDesc('votes')->get();

        // Si no hay capacidades, crear las predefinidas
        if ($capabilities->isEmpty()) {
            foreach ($this->defaults as $i => $title) {
                Capability::create([
                    'project_id'  => $project->id,
                    'title'       => $title,
                    'is_custom'   => false,
                    'proposed_by' => auth()->id(),
                ]);
            }
            $capabilities = Capability::where('project_id', $project->id)->get();
        }

        $myVotes = CapabilityVote::where('user_id', auth()->id())
            ->whereIn('capability_id', $capabilities->pluck('id'))->pluck('capability_id');

        return Inertia::render('Strategy/Capabilities', [
            'project'      => $project->load('organization'),
            'capabilities' => $capabilities,
            'myVotes'      => $myVotes,
        ]);
    }

    public function vote(Request $request, Project $project, Capability $capability)
    {
        $existing = CapabilityVote::where('capability_id', $capability->id)
            ->where('user_id', auth()->id())->first();

        if ($existing) {
            $existing->delete();
        } else {
            CapabilityVote::create(['capability_id' => $capability->id, 'user_id' => auth()->id()]);
        }

        $totalParticipants = 5; // Ajustar según proyecto
        $capability->recalculate($totalParticipants);

        broadcast(new VoteSubmitted($project->id, 'capability', $capability->id, [
            'votes'            => $capability->votes,
            'vote_percentage'  => $capability->vote_percentage,
        ]))->toOthers();

        return response()->json(['capability' => $capability->fresh(), 'voted' => !$existing]);
    }

    public function store(Request $request, Project $project)
    {
        $request->validate(['title' => 'required|string|max:300']);

        $capability = Capability::create([
            'project_id'  => $project->id,
            'title'       => $request->title,
            'is_custom'   => true,
            'proposed_by' => auth()->id(),
        ]);

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

# ── 11. ASPIRATION CONTROLLER ────────────────────────────────────
cat > app/Http/Controllers/AspirationController.php << 'PHP'
<?php
namespace App\Http\Controllers;

use App\Jobs\AspirationSynthesisJob;
use App\Models\Aspiration;
use App\Models\AiMessage;
use App\Models\Project;
use App\Events\VoteSubmitted;
use Illuminate\Http\Request;
use Inertia\Inertia;

class AspirationController extends Controller
{
    public function index(Project $project)
    {
        $aspirations = Aspiration::where('project_id', $project->id)
            ->with('user:id,name')->get();

        $latestAnalysis = AiMessage::where('project_id', $project->id)
            ->where('module', 'synthesis')
            ->whereJsonContains('context->type', 'aspiration')
            ->latest()->first();

        return Inertia::render('Strategy/Aspiration', [
            'project'        => $project->load('organization'),
            'aspirations'    => $aspirations,
            'myAspiration'   => $aspirations->firstWhere('user_id', auth()->id()),
            'latestAnalysis' => $latestAnalysis?->response,
        ]);
    }

    public function store(Request $request, Project $project)
    {
        $request->validate(['content' => 'required|string|max:500']);

        $aspiration = Aspiration::updateOrCreate(
            ['project_id' => $project->id, 'user_id' => auth()->id()],
            ['content' => $request->content]
        );

        // Broadcast para que aparezca en pantalla grande
        broadcast(new VoteSubmitted($project->id, 'aspiration', $aspiration->id, [
            'content'  => $aspiration->content,
            'count'    => Aspiration::where('project_id', $project->id)->count(),
        ]))->toOthers();

        // Si hay al menos 3 aspiraciones, lanzar síntesis de IA
        $count = Aspiration::where('project_id', $project->id)->count();
        if ($count >= 3) {
            AspirationSynthesisJob::dispatch($project);
        }

        return response()->json(['aspiration' => $aspiration, 'total' => $count]);
    }
}
PHP

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

// ── Estrategia Emergente Routes ──────────────────────────────────
use App\Http\Controllers\StrategicBetController;
use App\Http\Controllers\RenunciationController;
use App\Http\Controllers\CapabilityController;
use App\Http\Controllers\AspirationController;

Route::middleware(['auth', 'verified'])->prefix('projects/{project}/strategy')->group(function () {
    // Aspiración Ganadora
    Route::get('/aspiration',           [AspirationController::class, 'index'])->name('strategy.aspiration');
    Route::post('/aspiration',          [AspirationController::class, 'store'])->name('strategy.aspiration.store');

    // Capacidades Distintivas
    Route::get('/capabilities',         [CapabilityController::class, 'index'])->name('strategy.capabilities');
    Route::post('/capabilities',        [CapabilityController::class, 'store'])->name('strategy.capabilities.store');
    Route::post('/capabilities/{capability}/vote', [CapabilityController::class, 'vote'])->name('strategy.capabilities.vote');

    // Apuestas Estratégicas
    Route::get('/bets',                 [StrategicBetController::class, 'index'])->name('strategy.bets');
    Route::post('/bets',                [StrategicBetController::class, 'store'])->name('strategy.bets.store');
    Route::post('/bets/{bet}/vote',     [StrategicBetController::class, 'vote'])->name('strategy.bets.vote');
    Route::post('/bets/{bet}/activate', [StrategicBetController::class, 'activate'])->name('strategy.bets.activate');
    Route::post('/bets/{bet}/discard',  [StrategicBetController::class, 'discard'])->name('strategy.bets.discard');

    // Renuncias Estratégicas
    Route::get('/renunciations',             [RenunciationController::class, 'index'])->name('strategy.renunciations');
    Route::post('/renunciations',            [RenunciationController::class, 'store'])->name('strategy.renunciations.store');
    Route::post('/renunciations/{renunciation}/vote', [RenunciationController::class, 'vote'])->name('strategy.renunciations.vote');
    Route::get('/renunciations/{renunciation}/my-vote', [RenunciationController::class, 'myVote'])->name('strategy.renunciations.my-vote');
});
PHP

# ── 13. VISTAS VUE ───────────────────────────────────────────────
mkdir -p resources/js/Pages/Strategy

# Vista Aspiración Ganadora
cat > resources/js/Pages/Strategy/Aspiration.vue << 'VUE'
<script setup>
import { ref, computed } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    aspirations: Array,
    myAspiration: Object,
    latestAnalysis: String,
})

const content = ref(props.myAspiration?.content || '')
const saving = ref(false)
const saved = ref(!!props.myAspiration)
const localAspirations = ref([...props.aspirations])
const analysis = ref(props.latestAnalysis || null)

// Escuchar nuevas aspiraciones y síntesis en tiempo real
import { onMounted, onUnmounted } from 'vue'
let channel = null
onMounted(() => {
    if (window.Echo) {
        channel = window.Echo.channel(`project.${props.project.id}.strategy`)
            .listen('.vote.submitted', (data) => {
                if (data.type === 'aspiration') {
                    // Actualizar conteo
                }
            })
            .listen('.block.completed', (data) => {
                if (data.block === 'aspiration') {
                    analysis.value = data.ai_analysis
                }
            })
    }
})
onUnmounted(() => channel?.stopListening('.vote.submitted').stopListening('.block.completed'))

const save = () => {
    if (!content.value.trim()) return
    saving.value = true
    router.post(route('strategy.aspiration.store', props.project.id),
        { content: content.value },
        {
            onSuccess: () => { saved.value = true; saving.value = false },
            onError: () => { saving.value = false },
            preserveState: true,
        }
    )
}
</script>

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

            <div class="mb-8">
                <h1 class="text-2xl font-bold text-gray-900">Aspiración Ganadora</h1>
                <p class="text-gray-500 mt-1">{{ project.name }}</p>
                <p class="text-sm text-gray-400 mt-2">Completa la frase: "{{ project.organization?.name }} gana cuando..."</p>
            </div>

            <!-- Mi respuesta -->
            <div class="bg-white rounded-2xl shadow p-6 mb-6">
                <label class="block text-sm font-semibold text-gray-700 mb-2">
                    {{ project.organization?.name }} <span class="text-green-600 font-bold">gana cuando...</span>
                </label>
                <textarea
                    v-model="content"
                    rows="4"
                    placeholder="Escribe tu visión de lo que significa ganar para la organización..."
                    class="w-full border border-gray-300 rounded-xl p-3 text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent resize-none"
                ></textarea>
                <div class="flex justify-between items-center mt-3">
                    <span v-if="saved" class="text-sm text-green-600 font-medium">✅ Tu respuesta fue registrada</span>
                    <span v-else class="text-sm text-gray-400">{{ content.length }}/500 caracteres</span>
                    <button @click="save" :disabled="saving || !content.trim()"
                        class="bg-green-600 hover:bg-green-700 disabled:bg-gray-300 text-white px-5 py-2 rounded-xl text-sm font-medium transition-colors">
                        {{ saving ? 'Guardando...' : saved ? 'Actualizar' : 'Enviar mi visión' }}
                    </button>
                </div>
            </div>

            <!-- Todas las respuestas (anónimas) -->
            <div v-if="localAspirations.length > 0" class="bg-white rounded-2xl shadow p-6 mb-6">
                <h2 class="text-sm font-semibold text-gray-500 uppercase mb-4">
                    {{ localAspirations.length }} respuesta(s) del equipo
                </h2>
                <div class="space-y-3">
                    <div v-for="(a, i) in localAspirations" :key="a.id"
                         class="bg-gray-50 rounded-xl p-4 border-l-4 border-green-400">
                        <p class="text-sm text-gray-700 italic">"{{ a.content }}"</p>
                    </div>
                </div>
            </div>

            <!-- Análisis IA -->
            <div v-if="analysis" class="bg-gray-900 text-white rounded-2xl p-6">
                <div class="flex items-center gap-2 mb-4">
                    <span class="bg-purple-500 text-white text-xs px-2 py-1 rounded font-bold">CLAUDE AI</span>
                    <span class="font-semibold">Síntesis de las aspiraciones</span>
                </div>
                <div class="text-gray-300 text-sm leading-relaxed whitespace-pre-line">{{ analysis }}</div>
            </div>
            <div v-else-if="localAspirations.length >= 3" class="bg-gray-50 rounded-2xl p-6 text-center text-gray-500">
                <p class="text-sm">⏳ Claude está sintetizando las aspiraciones...</p>
            </div>

        </div>
    </AppLayout>
</template>
VUE

# Vista Capacidades
cat > resources/js/Pages/Strategy/Capabilities.vue << 'VUE'
<script setup>
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    capabilities: Array,
    myVotes: Array,
})

const myVotes = ref([...props.myVotes])
const capabilities = ref([...props.capabilities])
const newCapability = ref('')
const addingNew = ref(false)

const toggleVote = (capability) => {
    router.post(route('strategy.capabilities.vote', [props.project.id, capability.id]), {}, {
        preserveState: true,
        onSuccess: (page) => {
            myVotes.value = page.props.myVotes || myVotes.value
        }
    })
}

const hasVoted = (id) => myVotes.value.includes(id)
const maxVotes = Math.max(...props.capabilities.map(c => c.votes), 1)

const addCustom = () => {
    if (!newCapability.value.trim()) return
    router.post(route('strategy.capabilities.store', props.project.id),
        { title: newCapability.value },
        {
            onSuccess: () => { newCapability.value = ''; addingNew.value = false },
            preserveState: false,
        }
    )
}
</script>

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

            <div class="mb-8">
                <h1 class="text-2xl font-bold text-gray-900">Capacidades Distintivas</h1>
                <p class="text-gray-400 mt-1 text-sm">Vota las 3 capacidades donde {{ project.organization?.name }} es genuinamente único</p>
            </div>

            <div class="space-y-3 mb-6">
                <div v-for="cap in capabilities" :key="cap.id"
                     @click="toggleVote(cap)"
                     :class="['rounded-xl p-4 cursor-pointer border-2 transition-all',
                         hasVoted(cap.id)
                             ? 'border-green-500 bg-green-50'
                             : 'border-gray-200 bg-white hover:border-gray-300']">
                    <div class="flex items-center justify-between gap-3">
                        <div class="flex items-center gap-3 flex-1">
                            <div :class="['w-6 h-6 rounded-full border-2 flex items-center justify-center shrink-0',
                                hasVoted(cap.id) ? 'border-green-500 bg-green-500' : 'border-gray-300']">
                                <span v-if="hasVoted(cap.id)" class="text-white text-xs">✓</span>
                            </div>
                            <span class="text-sm font-medium text-gray-800">{{ cap.title }}</span>
                        </div>
                        <div class="flex items-center gap-2 shrink-0">
                            <div class="w-20 bg-gray-100 rounded-full h-1.5">
                                <div class="bg-green-500 h-1.5 rounded-full" :style="`width: ${(cap.votes/maxVotes)*100}%`"></div>
                            </div>
                            <span class="text-xs font-bold text-gray-600 w-4">{{ cap.votes }}</span>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Agregar capacidad personalizada -->
            <div class="bg-white rounded-xl border-2 border-dashed border-gray-300 p-4">
                <div v-if="!addingNew" class="text-center">
                    <button @click="addingNew = true" class="text-sm text-gray-500 hover:text-gray-700 font-medium">
                        + Agregar otra capacidad
                    </button>
                </div>
                <div v-else class="flex gap-2">
                    <input v-model="newCapability" placeholder="Describe la capacidad..."
                           class="flex-1 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-green-500" />
                    <button @click="addCustom" class="bg-green-600 text-white px-3 py-2 rounded-lg text-sm font-medium">Agregar</button>
                    <button @click="addingNew = false" class="text-gray-500 px-3 py-2 text-sm">Cancelar</button>
                </div>
            </div>

        </div>
    </AppLayout>
</template>
VUE

# Vista Apuestas
cat > resources/js/Pages/Strategy/Bets.vue << 'VUE'
<script setup>
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    bets: Array,
})

const bets = ref([...props.bets])
const showForm = ref(false)
const form = ref({ hypothesis: '', capability: '', value_logic: '' })
const submitting = ref(false)

const submit = () => {
    submitting.value = true
    router.post(route('strategy.bets.store', props.project.id), form.value, {
        onSuccess: () => { showForm.value = false; form.value = { hypothesis: '', capability: '', value_logic: '' }; submitting.value = false },
        onError: () => { submitting.value = false },
        preserveState: false,
    })
}

const vote = (bet, feasibility, impact) => {
    router.post(route('strategy.bets.vote', [props.project.id, bet.id]),
        { feasibility, impact },
        { preserveState: false }
    )
}

const statusColors = {
    proposed: 'bg-yellow-100 text-yellow-800',
    active: 'bg-green-100 text-green-800',
    discarded: 'bg-red-100 text-red-800',
}
</script>

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

            <div class="flex justify-between items-center mb-8">
                <div>
                    <h1 class="text-2xl font-bold text-gray-900">Apuestas Estratégicas</h1>
                    <p class="text-gray-400 text-sm mt-1">Si hacemos X, entonces Y ocurrirá</p>
                </div>
                <button @click="showForm = !showForm"
                        class="bg-blue-600 text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-blue-700">
                    + Proponer apuesta
                </button>
            </div>

            <!-- Formulario nueva apuesta -->
            <div v-if="showForm" class="bg-white rounded-2xl shadow p-6 mb-6 border-2 border-blue-200">
                <h2 class="font-semibold mb-4">Nueva apuesta estratégica</h2>
                <div class="space-y-3">
                    <div>
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Hipótesis *</label>
                        <textarea v-model="form.hypothesis" rows="2" placeholder='Si [acción] entonces [resultado esperado]...'
                                  class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-blue-500"></textarea>
                    </div>
                    <div>
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Capacidad relacionada</label>
                        <input v-model="form.capability" placeholder="¿En qué capacidad distintiva se apoya?"
                               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">Lógica de valor</label>
                        <input v-model="form.value_logic" placeholder="¿Por qué esto crearía valor para los asociados?"
                               class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:ring-2 focus:ring-blue-500" />
                    </div>
                    <div class="flex gap-2 pt-2">
                        <button @click="submit" :disabled="submitting || !form.hypothesis.trim()"
                                class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium disabled:bg-gray-300">
                            {{ submitting ? 'Enviando...' : 'Proponer y analizar con IA' }}
                        </button>
                        <button @click="showForm = false" class="text-gray-500 px-4 py-2 text-sm">Cancelar</button>
                    </div>
                </div>
            </div>

            <!-- Lista de apuestas -->
            <div class="space-y-4">
                <div v-for="bet in bets" :key="bet.id" class="bg-white rounded-2xl shadow p-6">
                    <div class="flex justify-between items-start mb-3">
                        <span :class="['text-xs px-2 py-1 rounded-full font-bold', statusColors[bet.status]]">
                            {{ bet.status }}
                        </span>
                        <span v-if="bet.score > 0" class="text-2xl font-black text-gray-800">{{ bet.score.toFixed(1) }}/5</span>
                    </div>

                    <p class="font-medium text-gray-800 mb-2">{{ bet.hypothesis }}</p>
                    <p v-if="bet.capability" class="text-sm text-gray-500 mb-3">Capacidad: {{ bet.capability }}</p>

                    <!-- Scores -->
                    <div v-if="bet.feasibility_avg > 0" class="flex gap-4 mb-3 text-sm">
                        <span class="text-gray-500">Viabilidad: <strong>{{ bet.feasibility_avg }}/5</strong></span>
                        <span class="text-gray-500">Impacto: <strong>{{ bet.impact_avg }}/5</strong></span>
                        <span class="text-gray-400">(score = 0.4×viabilidad + 0.6×impacto)</span>
                    </div>

                    <!-- Votación -->
                    <div v-if="bet.status === 'proposed'" class="border-t pt-3 mt-3">
                        <p class="text-xs text-gray-500 mb-2">Tu voto (viabilidad / impacto de 1 a 5):</p>
                        <div class="flex gap-2 flex-wrap">
                            <div v-for="val in [1,2,3,4,5]" :key="val" class="flex gap-1">
                                <button @click="vote(bet, val, val)"
                                        class="w-8 h-8 rounded-lg border text-xs font-bold hover:bg-blue-50 hover:border-blue-400">
                                    {{ val }}
                                </button>
                            </div>
                        </div>
                    </div>

                    <!-- Análisis IA -->
                    <div v-if="bet.ai_validation" class="mt-3 bg-gray-50 rounded-xl p-3 border border-gray-200">
                        <span class="text-xs font-bold text-purple-600">ANÁLISIS CLAUDE</span>
                        <div class="text-xs text-gray-600 mt-1 whitespace-pre-line leading-relaxed">{{ bet.ai_validation }}</div>
                    </div>
                    <div v-else-if="bet.status === 'proposed'" class="mt-2 text-xs text-gray-400 italic">
                        ⏳ Claude está analizando esta apuesta...
                    </div>
                </div>

                <div v-if="bets.length === 0" class="text-center py-12 text-gray-400">
                    <p class="text-lg mb-2">Sin apuestas aún</p>
                    <p class="text-sm">Sé el primero en proponer una apuesta estratégica</p>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# Vista Renuncias
cat > resources/js/Pages/Strategy/Renunciations.vue << 'VUE'
<script setup>
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    renunciations: Array,
})

const renunciations = ref([...props.renunciations])
const showForm = ref(false)
const form = ref({ description: '', justification: '', threshold: 70 })
const submitting = ref(false)

const submit = () => {
    submitting.value = true
    router.post(route('strategy.renunciations.store', props.project.id), form.value, {
        onSuccess: () => { showForm.value = false; form.value = { description: '', justification: '', threshold: 70 }; submitting.value = false },
        onError: () => { submitting.value = false },
        preserveState: false,
    })
}

const vote = (renunciation, voteValue) => {
    router.post(route('strategy.renunciations.vote', [props.project.id, renunciation.id]),
        { vote: voteValue },
        { preserveState: false }
    )
}
</script>

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

            <div class="flex justify-between items-center mb-6">
                <div>
                    <h1 class="text-2xl font-bold text-gray-900">Renuncias Estratégicas</h1>
                    <p class="text-gray-400 text-sm mt-1">La estrategia es elegir qué NO hacer</p>
                </div>
                <button @click="showForm = !showForm"
                        class="bg-red-600 text-white px-4 py-2 rounded-xl text-sm font-medium hover:bg-red-700">
                    + Proponer renuncia
                </button>
            </div>

            <!-- Formulario -->
            <div v-if="showForm" class="bg-white rounded-2xl shadow p-6 mb-6 border-2 border-red-200">
                <h2 class="font-semibold mb-4">Nueva renuncia estratégica</h2>
                <div class="space-y-3">
                    <div>
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">¿A qué renunciamos? *</label>
                        <textarea v-model="form.description" rows="2"
                                  placeholder="FEDEF NO hará / NO priorizará..."
                                  class="w-full border border-gray-300 rounded-lg p-2 text-sm"></textarea>
                    </div>
                    <div>
                        <label class="text-xs font-semibold text-gray-600 uppercase block mb-1">Justificación</label>
                        <input v-model="form.justification" placeholder="¿Por qué es mejor no hacerlo?"
                               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">Umbral de aprobación: {{ form.threshold }}%</label>
                        <input type="range" v-model.number="form.threshold" min="50" max="100" step="5" class="w-full" />
                    </div>
                    <div class="flex gap-2 pt-2">
                        <button @click="submit" :disabled="submitting || !form.description.trim()"
                                class="bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium disabled:bg-gray-300">
                            {{ submitting ? 'Enviando...' : 'Proponer' }}
                        </button>
                        <button @click="showForm = false" class="text-gray-500 px-4 py-2 text-sm">Cancelar</button>
                    </div>
                </div>
            </div>

            <!-- Lista -->
            <div class="space-y-4">
                <div v-for="r in renunciations" :key="r.id"
                     :class="['rounded-2xl shadow p-5 border-l-4', r.formalized ? 'border-red-500 bg-red-50' : 'bg-white border-gray-200']">
                    <div class="flex justify-between items-start mb-2">
                        <span v-if="r.formalized" class="text-xs bg-red-600 text-white px-2 py-1 rounded-full font-bold">
                            ✓ FORMALIZADA
                        </span>
                        <span v-else class="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded-full">En votación</span>
                        <span class="text-lg font-black" :class="r.formalized ? 'text-red-700' : 'text-gray-600'">
                            {{ r.approval_rate }}%
                        </span>
                    </div>

                    <p class="font-medium text-gray-800 mb-1">{{ r.description }}</p>
                    <p v-if="r.justification" class="text-sm text-gray-500 mb-3">{{ r.justification }}</p>

                    <!-- Barra de progreso -->
                    <div class="mb-3">
                        <div class="flex justify-between text-xs text-gray-500 mb-1">
                            <span>{{ r.votes_yes }} a favor</span>
                            <span>{{ r.votes_no }} en contra</span>
                        </div>
                        <div class="bg-gray-200 rounded-full h-2">
                            <div class="bg-red-500 h-2 rounded-full transition-all"
                                 :style="`width: ${r.approval_rate}%`"></div>
                        </div>
                        <p class="text-xs text-gray-400 mt-1">Umbral requerido: {{ r.threshold }}%</p>
                    </div>

                    <!-- Botones de voto (anónimos) -->
                    <div v-if="!r.formalized" class="flex gap-2">
                        <button @click="vote(r, true)"
                                class="flex-1 bg-red-100 hover:bg-red-200 text-red-700 font-semibold py-2 rounded-lg text-sm transition-colors">
                            👍 Sí, renunciamos
                        </button>
                        <button @click="vote(r, false)"
                                class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-600 font-semibold py-2 rounded-lg text-sm transition-colors">
                            👎 No renunciamos
                        </button>
                    </div>
                </div>

                <div v-if="renunciations.length === 0" class="text-center py-12 text-gray-400">
                    <p>Sin renuncias propuestas aún</p>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# ── 14. 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 04 completado exitosamente"
echo ""
echo "  Módulos creados:"
echo "  ✅ Aspiración Ganadora (con síntesis IA)"
echo "  ✅ Capacidades Distintivas (votación)"
echo "  ✅ Apuestas Estratégicas (votos + IA)"
echo "  ✅ Renuncias Estratégicas (anónimas)"
echo "  ✅ BetValidationJob (Claude valida c/apuesta)"
echo "  ✅ AspirationSynthesisJob (Claude sintetiza)"
echo "  ✅ 3 nuevos eventos WebSocket"
echo "  ✅ 4 vistas Vue compiladas"
echo ""
echo "  Rutas disponibles:"
echo "  /projects/{id}/strategy/aspiration"
echo "  /projects/{id}/strategy/capabilities"
echo "  /projects/{id}/strategy/bets"
echo "  /projects/{id}/strategy/renunciations"
echo "========================================="
