#!/bin/bash
# ================================================================
# OKRFEDEF — Sprint 03: Módulo DOFA completo
# Ejecutar desde: /home/evolucionamos/public_html/estrategia
# Comando: bash sprint_03_dofa.sh
# ================================================================
set -e
echo "========================================="
echo "  OKRFEDEF — Sprint 03: Módulo DOFA"
echo "========================================="

# ── 1. INSTALAR DOMPDF PUBLISH ────────────────────────────────────
php artisan vendor:publish --provider="Barryvdh\DomPDF\ServiceProvider"

# ── 2. CONTROLLERS ───────────────────────────────────────────────
php artisan make:controller DofaController --resource
php artisan make:controller DofaSessionController
php artisan make:request StoreDofaFactorRequest
php artisan make:request RateDofaFactorRequest

# ── 3. JOBS Y EVENTOS ────────────────────────────────────────────
php artisan make:job DofaAnalysisJob
php artisan make:event DofaFactorRated
php artisan make:event DofaSessionCompleted

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

# ── 4. DOFA SERVICE ──────────────────────────────────────────────
mkdir -p app/Services

cat > app/Services/DofaService.php << 'PHP'
<?php
namespace App\Services;

use App\Models\DofaFactor;
use App\Models\DofaSession;
use App\Models\Project;

class DofaService
{
    /**
     * Calcula MEFI o MEFE según el tipo de factores
     * MEFI = factores F y D (internos)
     * MEFE = factores O y A (externos)
     * Fórmula: Σ(mefi_mefe_weight × mefi_mefe_rating)
     */
    public function calculateMefi(Project $project): float
    {
        $factors = DofaFactor::where('project_id', $project->id)
            ->whereIn('type', ['F', 'D'])
            ->whereNotNull('mefi_mefe_weight')
            ->whereNotNull('mefi_mefe_rating')
            ->get();

        if ($factors->isEmpty()) return 0.0;

        return round($factors->sum(fn($f) => $f->mefi_mefe_weight * $f->mefi_mefe_rating), 4);
    }

    public function calculateMefe(Project $project): float
    {
        $factors = DofaFactor::where('project_id', $project->id)
            ->whereIn('type', ['O', 'A'])
            ->whereNotNull('mefi_mefe_weight')
            ->whereNotNull('mefi_mefe_rating')
            ->get();

        if ($factors->isEmpty()) return 0.0;

        return round($factors->sum(fn($f) => $f->mefi_mefe_weight * $f->mefi_mefe_rating), 4);
    }

    /**
     * Calcula la matriz cruzada completa
     * score_FO = Σ(score_F_i × score_O_j) para todos los pares
     */
    public function calculateCrossMatrix(Project $project): array
    {
        $F = DofaFactor::where('project_id', $project->id)->where('type', 'F')->get();
        $D = DofaFactor::where('project_id', $project->id)->where('type', 'D')->get();
        $O = DofaFactor::where('project_id', $project->id)->where('type', 'O')->get();
        $A = DofaFactor::where('project_id', $project->id)->where('type', 'A')->get();

        $score_fo = $this->crossScore($F, $O);
        $score_fa = $this->crossScore($F, $A);
        $score_do = $this->crossScore($D, $O);
        $score_da = $this->crossScore($D, $A);

        // Determinar cuadrante dominante
        $scores = [
            'offensive'     => $score_fo, // FO: mayor → estrategia ofensiva
            'defensive'     => $score_fa, // FA: mayor → estrategia defensiva
            'reorientation' => $score_do, // DO: mayor → reorientación
            'survival'      => $score_da, // DA: mayor → supervivencia
        ];
        $quadrant = array_search(max($scores), $scores);

        return [
            'score_fo'  => round($score_fo, 4),
            'score_fa'  => round($score_fa, 4),
            'score_do'  => round($score_do, 4),
            'score_da'  => round($score_da, 4),
            'quadrant'  => $quadrant,
            'scores'    => $scores,
        ];
    }

    private function crossScore($groupA, $groupB): float
    {
        $total = 0;
        foreach ($groupA as $a) {
            foreach ($groupB as $b) {
                $total += $a->score * $b->score;
            }
        }
        return $total;
    }

    /**
     * Consolida las calificaciones de múltiples participantes
     * Promedio ponderado de importancia y prioridad por factor
     */
    public function consolidateRatings(DofaSession $session): void
    {
        $ratings = $session->participant_ratings ?? [];
        if (empty($ratings)) return;

        $consolidated = [];

        // Agrupar por factor_id
        foreach ($ratings as $userId => $userRatings) {
            foreach ($userRatings as $factorId => $rating) {
                if (!isset($consolidated[$factorId])) {
                    $consolidated[$factorId] = ['importance' => [], 'priority' => []];
                }
                $consolidated[$factorId]['importance'][] = $rating['importance'] ?? 0;
                $consolidated[$factorId]['priority'][]   = $rating['priority'] ?? 0;
            }
        }

        // Calcular promedios y actualizar factores
        foreach ($consolidated as $factorId => $values) {
            $factor = DofaFactor::find($factorId);
            if (!$factor) continue;

            $factor->importance = round(array_sum($values['importance']) / count($values['importance']), 4);
            $factor->priority   = round(array_sum($values['priority']) / count($values['priority']));
            $factor->score      = round($factor->importance * $factor->priority, 4);
            $factor->save();
        }

        $session->consolidated = $consolidated;
        $session->save();
    }

    /**
     * Calcula y guarda todos los resultados en la sesión
     */
    public function finalizeSession(DofaSession $session): DofaSession
    {
        $project = $session->project;

        $this->consolidateRatings($session);

        $cross = $this->calculateCrossMatrix($project);

        $session->mefi_total = $this->calculateMefi($project);
        $session->mefe_total = $this->calculateMefe($project);
        $session->score_fo   = $cross['score_fo'];
        $session->score_fa   = $cross['score_fa'];
        $session->score_do   = $cross['score_do'];
        $session->score_da   = $cross['score_da'];
        $session->quadrant   = $cross['quadrant'];
        $session->status     = 'completed';
        $session->save();

        return $session;
    }

    public function getQuadrantLabel(string $quadrant): string
    {
        return match($quadrant) {
            'offensive'     => 'Estrategia Ofensiva — Usar fortalezas para aprovechar oportunidades',
            'defensive'     => 'Estrategia Defensiva — Usar fortalezas para enfrentar amenazas',
            'reorientation' => 'Estrategia de Reorientación — Superar debilidades aprovechando oportunidades',
            'survival'      => 'Estrategia de Supervivencia — Reducir debilidades y evitar amenazas',
            default         => 'No determinado',
        };
    }
}
PHP

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

use App\Models\DofaFactor;
use App\Models\DofaSession;
use App\Models\Project;
use App\Services\DofaService;
use App\Jobs\DofaAnalysisJob;
use App\Events\DofaFactorRated;
use App\Events\DofaSessionCompleted;
use Illuminate\Http\Request;
use Inertia\Inertia;

class DofaController extends Controller
{
    public function __construct(private DofaService $dofaService) {}

    /**
     * Vista principal del DOFA para el consultor
     */
    public function index(Project $project)
    {
        $this->authorize('view_all_okrs');

        $factors  = DofaFactor::where('project_id', $project->id)->orderBy('type')->orderBy('order')->get();
        $session  = DofaSession::where('project_id', $project->id)->latest()->first();

        return Inertia::render('Dofa/Index', [
            'project'  => $project->load('organization'),
            'factors'  => $factors->groupBy('type'),
            'session'  => $session,
            'quadrant_label' => $session?->quadrant
                ? $this->dofaService->getQuadrantLabel($session->quadrant)
                : null,
        ]);
    }

    /**
     * Vista de calificación para participantes (celular)
     */
    public function rate(Project $project)
    {
        $session = DofaSession::where('project_id', $project->id)
            ->where('status', 'open')
            ->latest()
            ->firstOrFail();

        $factors = DofaFactor::where('project_id', $project->id)
            ->orderBy('type')->orderBy('order')->get();

        $myRatings = collect($session->participant_ratings ?? [])
            ->get(auth()->id(), []);

        return Inertia::render('Dofa/Rate', [
            'project'   => $project,
            'session'   => $session,
            'factors'   => $factors->groupBy('type'),
            'myRatings' => $myRatings,
        ]);
    }

    /**
     * Guardar calificación de un participante (API - desde celular)
     */
    public function submitRating(Request $request, Project $project)
    {
        $request->validate([
            'session_id'          => 'required|exists:dofa_sessions,id',
            'ratings'             => 'required|array',
            'ratings.*.factor_id' => 'required|exists:dofa_factors,id',
            'ratings.*.importance'=> 'required|numeric|min:0|max:1',
            'ratings.*.priority'  => 'required|integer|in:0,1,3,6,9',
        ]);

        $session  = DofaSession::findOrFail($request->session_id);
        $userId   = auth()->id();
        $ratings  = $session->participant_ratings ?? [];

        // Guardar calificaciones del usuario
        $ratings[$userId] = collect($request->ratings)
            ->keyBy('factor_id')
            ->map(fn($r) => [
                'importance' => $r['importance'],
                'priority'   => $r['priority'],
            ])
            ->toArray();

        $session->participant_ratings = $ratings;
        $session->save();

        // Broadcast para actualizar monitor en tiempo real
        broadcast(new DofaFactorRated($project->id, $userId, count($ratings)))->toOthers();

        // Contar participantes esperados (usuarios con rol en el proyecto)
        $expectedCount = $project->organization->projects()
            ->with('cycles.objectives')
            ->count() > 0 ? 5 : 5; // Ajustar según participantes registrados

        // Si todos calificaron, finalizar sesión y lanzar análisis IA
        $connected = session('live_session_connected_count', $expectedCount);
        if (count($ratings) >= $connected) {
            $this->dofaService->finalizeSession($session);
            DofaAnalysisJob::dispatch($session);
            broadcast(new DofaSessionCompleted($session))->toOthers();
        }

        return response()->json([
            'success'          => true,
            'rated_count'      => count($ratings),
            'session_status'   => $session->fresh()->status,
        ]);
    }

    /**
     * Vista monitor para el consultor (video beam)
     */
    public function monitor(Project $project)
    {
        $session = DofaSession::where('project_id', $project->id)
            ->latest()->firstOrFail();

        return Inertia::render('Dofa/Monitor', [
            'project' => $project,
            'session' => $session,
            'factors' => DofaFactor::where('project_id', $project->id)
                ->orderBy('type')->orderBy('order')->get()->groupBy('type'),
        ]);
    }

    /**
     * Resultados finales del DOFA
     */
    public function results(Project $project)
    {
        $session = DofaSession::where('project_id', $project->id)
            ->where('status', 'completed')->latest()->firstOrFail();

        $factors = DofaFactor::where('project_id', $project->id)
            ->orderBy('type')->orderBy('order')->get();

        $mefi = $this->dofaService->calculateMefi($project);
        $mefe = $this->dofaService->calculateMefe($project);
        $cross = $this->dofaService->calculateCrossMatrix($project);

        return Inertia::render('Dofa/Results', [
            'project'        => $project->load('organization'),
            'session'        => $session,
            'factors'        => $factors->groupBy('type'),
            'mefi'           => $mefi,
            'mefe'           => $mefe,
            'cross'          => $cross,
            'quadrant_label' => $this->dofaService->getQuadrantLabel($session->quadrant),
        ]);
    }

    /**
     * Iniciar nueva sesión DOFA
     */
    public function startSession(Project $project)
    {
        $session = DofaSession::create([
            'project_id' => $project->id,
            'status'     => 'open',
        ]);

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

    /**
     * Crear o actualizar un factor DOFA
     */
    public function storeFactor(Request $request, Project $project)
    {
        $request->validate([
            'type'              => 'required|in:F,D,O,A',
            'description'       => 'required|string|max:500',
            'mefi_mefe_rating'  => 'nullable|integer|in:1,2,3,4',
            'mefi_mefe_weight'  => 'nullable|numeric|min:0|max:1',
            'order'             => 'nullable|integer',
        ]);

        $factor = DofaFactor::create([
            'project_id'        => $project->id,
            'type'              => $request->type,
            'description'       => $request->description,
            'mefi_mefe_rating'  => $request->mefi_mefe_rating,
            'mefi_mefe_weight'  => $request->mefi_mefe_weight,
            'created_by'        => auth()->id(),
            'order'             => $request->order ?? 0,
        ]);

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

    /**
     * Exportar DOFA a PDF
     */
    public function exportPdf(Project $project)
    {
        $session = DofaSession::where('project_id', $project->id)
            ->where('status', 'completed')->latest()->first();

        $factors = DofaFactor::where('project_id', $project->id)
            ->orderBy('type')->orderBy('order')->get()->groupBy('type');

        $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pdf.dofa', [
            'project'        => $project->load('organization'),
            'session'        => $session,
            'factors'        => $factors,
            'mefi'           => $this->dofaService->calculateMefi($project),
            'mefe'           => $this->dofaService->calculateMefe($project),
            'cross'          => $this->dofaService->calculateCrossMatrix($project),
            'quadrant_label' => $session ? $this->dofaService->getQuadrantLabel($session->quadrant) : '',
        ]);

        return $pdf->download("DOFA_{$project->id}.pdf");
    }
}
PHP

# ── 6. DOFA ANALYSIS JOB (Claude API) ───────────────────────────
cat > app/Jobs/DofaAnalysisJob.php << 'PHP'
<?php
namespace App\Jobs;

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

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

    public function __construct(public DofaSession $session) {}

    public function handle(): void
    {
        $project = $this->session->project->load('organization');
        $factors = DofaFactor::where('project_id', $project->id)
            ->orderBy('type')->orderBy('order')->get();

        $factorsList = $factors->groupBy('type')->map(fn($group) =>
            $group->map(fn($f) => "- {$f->description} (score: {$f->score})")->join("\n")
        );

        $prompt = <<<PROMPT
Eres un consultor estratégico experto en el sector solidario colombiano, específicamente en fondos de empleados.

Estás analizando los resultados del DOFA de {$project->organization->name} — {$project->name}.

RESULTADOS DE LA SESIÓN:
- MEFI (Factores Internos): {$this->session->mefi_total}
- MEFE (Factores Externos): {$this->session->mefe_total}
- Score FO (Fortalezas × Oportunidades): {$this->session->score_fo}
- Score FA (Fortalezas × Amenazas): {$this->session->score_fa}
- Score DO (Debilidades × Oportunidades): {$this->session->score_do}
- Score DA (Debilidades × Amenazas): {$this->session->score_da}
- CUADRANTE DOMINANTE: {$this->session->quadrant}

FORTALEZAS:
{$factorsList->get('F', 'No registradas')}

DEBILIDADES:
{$factorsList->get('D', 'No registradas')}

OPORTUNIDADES:
{$factorsList->get('O', 'No registradas')}

AMENAZAS:
{$factorsList->get('A', 'No registradas')}

Por favor proporciona:

1. **POSICIONAMIENTO ESTRATÉGICO** (2 párrafos): Qué significa este cuadrante para la organización y qué implica para el plan estratégico.

2. **LAS 3 TENSIONES ESTRATÉGICAS MÁS IMPORTANTES**: Los 3 puntos de mayor tensión entre los factores, con una pregunta provocadora para el equipo sobre cada una.

3. **CAPACIDADES DISTINTIVAS DETECTADAS**: Qué hace única a esta organización según los factores registrados (máximo 3 puntos).

4. **PREGUNTA ESTRATÉGICA CENTRAL**: La pregunta más importante que el equipo directivo debe responder hoy para definir su estrategia.

Responde de forma directa, sin rodeos, con el tono de un consultor experimentado. Máximo 500 palabras en total.
PROMPT;

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

            // Guardar análisis en la sesión
            $this->session->ai_analysis = $content;
            $this->session->save();

            // Guardar en historial de mensajes IA
            AiMessage::create([
                'project_id'   => $project->id,
                'module'       => 'dofa',
                'context'      => [
                    'session_id' => $this->session->id,
                    'quadrant'   => $this->session->quadrant,
                    'mefi'       => $this->session->mefi_total,
                    'mefe'       => $this->session->mefe_total,
                ],
                'prompt'       => $prompt,
                'response'     => $content,
                'model'        => config('services.anthropic.model', 'claude-sonnet-4-20250514'),
                'tokens_used'  => $tokens,
                'trigger'      => 'proactive',
            ]);

            // Broadcast resultado a todos los conectados
            broadcast(new \App\Events\DofaSessionCompleted($this->session->fresh()));
        }
    }
}
PHP

# ── 7. EVENTOS BROADCAST ─────────────────────────────────────────
cat > app/Events/DofaFactorRated.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 DofaFactorRated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public int $projectId,
        public int $userId,
        public int $ratedCount
    ) {}

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

    public function broadcastAs(): string
    {
        return 'factor.rated';
    }

    public function broadcastWith(): array
    {
        return [
            'user_id'     => $this->userId,
            'rated_count' => $this->ratedCount,
        ];
    }
}
PHP

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

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

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

    public function __construct(public DofaSession $session) {}

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

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

    public function broadcastWith(): array
    {
        return [
            'session_id'    => $this->session->id,
            'quadrant'      => $this->session->quadrant,
            'mefi'          => $this->session->mefi_total,
            'mefe'          => $this->session->mefe_total,
            'score_fo'      => $this->session->score_fo,
            'score_fa'      => $this->session->score_fa,
            'score_do'      => $this->session->score_do,
            'score_da'      => $this->session->score_da,
            'ai_analysis'   => $this->session->ai_analysis,
        ];
    }
}
PHP

# ── 8. CONFIGURAR ANTHROPIC EN SERVICES ──────────────────────────
cat >> config/services.php << 'PHP'

    'anthropic' => [
        'api_key' => env('ANTHROPIC_API_KEY'),
        'model'   => env('ANTHROPIC_MODEL', 'claude-sonnet-4-20250514'),
        'max_tokens' => env('ANTHROPIC_MAX_TOKENS', 2000),
    ],

    'elevenlabs' => [
        'api_key'  => env('ELEVENLABS_API_KEY'),
        'voice_id' => env('ELEVENLABS_VOICE_ID'),
        'model'    => env('ELEVENLABS_MODEL', 'eleven_multilingual_v2'),
    ],
PHP

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

// ── DOFA Routes ──────────────────────────────────────────────────
use App\Http\Controllers\DofaController;

Route::middleware(['auth', 'verified'])->prefix('projects/{project}')->group(function () {
    Route::get('/dofa',              [DofaController::class, 'index'])->name('dofa.index');
    Route::get('/dofa/rate',         [DofaController::class, 'rate'])->name('dofa.rate');
    Route::get('/dofa/monitor',      [DofaController::class, 'monitor'])->name('dofa.monitor');
    Route::get('/dofa/results',      [DofaController::class, 'results'])->name('dofa.results');
    Route::get('/dofa/export-pdf',   [DofaController::class, 'exportPdf'])->name('dofa.export-pdf');
    Route::post('/dofa/start',       [DofaController::class, 'startSession'])->name('dofa.start');
    Route::post('/dofa/factor',      [DofaController::class, 'storeFactor'])->name('dofa.factor.store');
    Route::post('/dofa/submit',      [DofaController::class, 'submitRating'])->name('dofa.submit');
});
PHP

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

# Vista de calificación móvil (participantes desde celular)
cat > resources/js/Pages/Dofa/Rate.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,
    session: Object,
    factors: Object,
    myRatings: Object,
})

const typeLabels = { F: 'Fortalezas', D: 'Debilidades', O: 'Oportunidades', A: 'Amenazas' }
const typeColors = { F: 'bg-green-50 border-green-300', D: 'bg-red-50 border-red-300', O: 'bg-blue-50 border-blue-300', A: 'bg-orange-50 border-orange-300' }
const priorityOptions = [
    { value: 0, label: 'Ninguna' },
    { value: 1, label: 'Baja' },
    { value: 3, label: 'Regular' },
    { value: 6, label: 'Media' },
    { value: 9, label: 'Alta' },
]

const ratings = ref({})
const submitting = ref(false)
const submitted = ref(false)

// Inicializar con calificaciones previas si existen
Object.entries(props.myRatings || {}).forEach(([factorId, rating]) => {
    ratings.value[factorId] = { importance: rating.importance, priority: rating.priority }
})

// Inicializar factores no calificados
Object.values(props.factors || {}).flat().forEach(factor => {
    if (!ratings.value[factor.id]) {
        ratings.value[factor.id] = { importance: 0.05, priority: 6 }
    }
})

const allFactors = computed(() => Object.values(props.factors || {}).flat())
const ratedCount = computed(() => Object.keys(ratings.value).length)
const totalFactors = computed(() => allFactors.value.length)
const progress = computed(() => totalFactors.value > 0 ? Math.round((ratedCount.value / totalFactors.value) * 100) : 0)

const submit = () => {
    submitting.value = true
    const ratingsArray = Object.entries(ratings.value).map(([factorId, rating]) => ({
        factor_id: parseInt(factorId),
        importance: parseFloat(rating.importance),
        priority: parseInt(rating.priority),
    }))

    router.post(route('dofa.submit', props.project.id), {
        session_id: props.session.id,
        ratings: ratingsArray,
    }, {
        onSuccess: () => { submitted.value = true; submitting.value = false },
        onError: () => { submitting.value = false },
    })
}
</script>

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

            <!-- Header -->
            <div class="mb-6">
                <h1 class="text-2xl font-bold text-gray-900">Calificación DOFA</h1>
                <p class="text-gray-500 mt-1">{{ project.name }}</p>
                <div class="mt-3 bg-gray-100 rounded-full h-2">
                    <div class="bg-green-500 h-2 rounded-full transition-all" :style="`width: ${progress}%`"></div>
                </div>
                <p class="text-sm text-gray-500 mt-1">{{ ratedCount }}/{{ totalFactors }} factores calificados</p>
            </div>

            <!-- Submitted state -->
            <div v-if="submitted" class="bg-green-50 border border-green-200 rounded-xl p-8 text-center">
                <div class="text-5xl mb-4">✅</div>
                <h2 class="text-xl font-semibold text-green-800">¡Calificación enviada!</h2>
                <p class="text-green-600 mt-2">Tus respuestas han sido registradas. El consultor recibirá los resultados.</p>
            </div>

            <!-- Rating form -->
            <div v-else>
                <div v-for="(factorList, type) in factors" :key="type" class="mb-8">
                    <h2 class="text-lg font-semibold mb-3 flex items-center gap-2">
                        <span :class="['px-3 py-1 rounded-full text-sm font-bold', type === 'F' ? 'bg-green-100 text-green-800' : type === 'D' ? 'bg-red-100 text-red-800' : type === 'O' ? 'bg-blue-100 text-blue-800' : 'bg-orange-100 text-orange-800']">
                            {{ typeLabels[type] }}
                        </span>
                    </h2>

                    <div v-for="factor in factorList" :key="factor.id" :class="['border rounded-xl p-4 mb-4', typeColors[type]]">
                        <p class="text-sm font-medium text-gray-800 mb-4">{{ factor.description }}</p>

                        <!-- Importancia (slider) -->
                        <div class="mb-4">
                            <div class="flex justify-between items-center mb-1">
                                <label class="text-xs font-semibold text-gray-600 uppercase tracking-wide">Importancia</label>
                                <span class="text-sm font-bold text-gray-800">{{ (ratings[factor.id]?.importance * 100).toFixed(0) }}%</span>
                            </div>
                            <input
                                type="range" min="0" max="0.2" step="0.01"
                                v-model.number="ratings[factor.id].importance"
                                class="w-full h-2 rounded-lg appearance-none cursor-pointer"
                            />
                            <div class="flex justify-between text-xs text-gray-400 mt-1">
                                <span>0%</span><span>10%</span><span>20%</span>
                            </div>
                        </div>

                        <!-- Prioridad (botones) -->
                        <div>
                            <label class="text-xs font-semibold text-gray-600 uppercase tracking-wide block mb-2">Prioridad</label>
                            <div class="flex gap-2 flex-wrap">
                                <button
                                    v-for="opt in priorityOptions" :key="opt.value"
                                    @click="ratings[factor.id].priority = opt.value"
                                    :class="['px-3 py-2 rounded-lg text-sm font-medium border-2 transition-all',
                                        ratings[factor.id]?.priority === opt.value
                                            ? 'border-gray-800 bg-gray-800 text-white'
                                            : 'border-gray-300 bg-white text-gray-600 hover:border-gray-500']"
                                >
                                    {{ opt.label }} ({{ opt.value }})
                                </button>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Submit button -->
                <div class="sticky bottom-4">
                    <button
                        @click="submit"
                        :disabled="submitting"
                        class="w-full bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white font-bold py-4 px-6 rounded-xl text-lg transition-colors shadow-lg"
                    >
                        {{ submitting ? 'Enviando...' : 'Enviar mi calificación' }}
                    </button>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# Vista Monitor (video beam - consultor)
cat > resources/js/Pages/Dofa/Monitor.vue << 'VUE'
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import AppLayout from '@/Layouts/AppLayout.vue'

const props = defineProps({
    project: Object,
    session: Object,
    factors: Object,
})

const ratedCount = ref(0)
const sessionStatus = ref(props.session?.status || 'open')
const aiAnalysis = ref(props.session?.ai_analysis || null)
const sessionData = ref(null)

// Escuchar eventos en tiempo real
let channel = null
onMounted(() => {
    if (window.Echo) {
        channel = window.Echo.channel(`project.${props.project.id}.dofa`)
            .listen('.factor.rated', (data) => {
                ratedCount.value = data.rated_count
            })
            .listen('.session.completed', (data) => {
                sessionStatus.value = 'completed'
                sessionData.value = data
                aiAnalysis.value = data.ai_analysis
            })
    }
})
onUnmounted(() => { channel?.stopListening('.factor.rated'); channel?.stopListening('.session.completed') })

const allFactors = Object.values(props.factors || {}).flat()
const totalFactors = allFactors.length

const quadrantColors = {
    offensive: 'bg-green-100 border-green-500 text-green-800',
    defensive: 'bg-blue-100 border-blue-500 text-blue-800',
    reorientation: 'bg-yellow-100 border-yellow-500 text-yellow-800',
    survival: 'bg-red-100 border-red-500 text-red-800',
}
</script>

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

            <div class="flex justify-between items-center mb-8">
                <div>
                    <h1 class="text-3xl font-bold text-gray-900">Monitor DOFA en Vivo</h1>
                    <p class="text-gray-500">{{ project.name }}</p>
                </div>
                <div :class="['px-4 py-2 rounded-full font-bold text-sm border-2',
                    sessionStatus === 'completed' ? 'bg-green-100 border-green-500 text-green-800' : 'bg-yellow-100 border-yellow-500 text-yellow-800']">
                    {{ sessionStatus === 'completed' ? '✅ Completado' : '🔴 En progreso' }}
                </div>
            </div>

            <!-- Progreso de participantes -->
            <div class="bg-white rounded-2xl shadow p-6 mb-6">
                <h2 class="text-lg font-semibold mb-4">Participantes que han calificado</h2>
                <div class="flex items-center gap-4">
                    <div class="text-6xl font-black text-green-600">{{ ratedCount }}</div>
                    <div>
                        <div class="text-2xl font-bold text-gray-400">participantes</div>
                        <div class="text-gray-400">han completado la calificación</div>
                    </div>
                </div>
                <div class="mt-4 bg-gray-100 rounded-full h-3">
                    <div class="bg-green-500 h-3 rounded-full transition-all duration-500" :style="`width: ${ratedCount > 0 ? Math.min(ratedCount * 20, 100) : 0}%`"></div>
                </div>
            </div>

            <!-- Resultados cuando completa -->
            <div v-if="sessionStatus === 'completed' && sessionData" class="grid grid-cols-2 gap-6 mb-6">
                <div class="bg-white rounded-2xl shadow p-6">
                    <h3 class="text-sm font-semibold text-gray-500 uppercase mb-3">MEFI / MEFE</h3>
                    <div class="grid grid-cols-2 gap-4">
                        <div class="text-center">
                            <div class="text-4xl font-black text-blue-600">{{ parseFloat(sessionData.mefi || 0).toFixed(2) }}</div>
                            <div class="text-sm text-gray-500 mt-1">MEFI (Interno)</div>
                        </div>
                        <div class="text-center">
                            <div class="text-4xl font-black text-purple-600">{{ parseFloat(sessionData.mefe || 0).toFixed(2) }}</div>
                            <div class="text-sm text-gray-500 mt-1">MEFE (Externo)</div>
                        </div>
                    </div>
                </div>

                <div class="bg-white rounded-2xl shadow p-6">
                    <h3 class="text-sm font-semibold text-gray-500 uppercase mb-3">Scores Cruzados</h3>
                    <div class="grid grid-cols-2 gap-2 text-sm">
                        <div class="flex justify-between"><span class="font-medium text-green-700">FO:</span><span class="font-bold">{{ parseFloat(sessionData.score_fo || 0).toFixed(2) }}</span></div>
                        <div class="flex justify-between"><span class="font-medium text-blue-700">FA:</span><span class="font-bold">{{ parseFloat(sessionData.score_fa || 0).toFixed(2) }}</span></div>
                        <div class="flex justify-between"><span class="font-medium text-yellow-700">DO:</span><span class="font-bold">{{ parseFloat(sessionData.score_do || 0).toFixed(2) }}</span></div>
                        <div class="flex justify-between"><span class="font-medium text-red-700">DA:</span><span class="font-bold">{{ parseFloat(sessionData.score_da || 0).toFixed(2) }}</span></div>
                    </div>
                </div>

                <!-- Cuadrante dominante -->
                <div :class="['col-span-2 rounded-2xl shadow p-6 border-2', quadrantColors[sessionData.quadrant] || 'bg-gray-100 border-gray-300']">
                    <h3 class="text-sm font-semibold uppercase mb-2">Cuadrante Dominante</h3>
                    <p class="text-2xl font-black capitalize">{{ sessionData.quadrant }}</p>
                </div>
            </div>

            <!-- Análisis IA -->
            <div v-if="aiAnalysis" class="bg-gradient-to-br from-gray-900 to-gray-800 text-white rounded-2xl shadow p-6">
                <div class="flex items-center gap-3 mb-4">
                    <div class="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center text-white font-bold text-sm">AI</div>
                    <h3 class="text-lg font-semibold">Análisis de Claude</h3>
                </div>
                <div class="prose prose-invert prose-sm max-w-none" v-html="aiAnalysis.replace(/\n/g, '<br>')"></div>
            </div>
            <div v-else-if="sessionStatus === 'completed'" class="bg-gray-50 rounded-2xl p-8 text-center text-gray-500">
                <div class="text-3xl mb-2">⏳</div>
                <p>Generando análisis con IA...</p>
            </div>

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

# Vista de resultados
cat > resources/js/Pages/Dofa/Results.vue << 'VUE'
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue'
import { Link } from '@inertiajs/vue3'

const props = defineProps({
    project: Object,
    session: Object,
    factors: Object,
    mefi: Number,
    mefe: Number,
    cross: Object,
    quadrant_label: String,
})

const typeLabels = { F: 'Fortalezas', D: 'Debilidades', O: 'Oportunidades', A: 'Amenazas' }
const typeColors = {
    F: 'bg-green-50 border-green-200',
    D: 'bg-red-50 border-red-200',
    O: 'bg-blue-50 border-blue-200',
    A: 'bg-orange-50 border-orange-200'
}
</script>

<template>
    <AppLayout :title="`Resultados DOFA — ${project.name}`">
        <div class="max-w-5xl mx-auto px-4 py-6">

            <div class="flex justify-between items-center mb-8">
                <div>
                    <h1 class="text-2xl font-bold text-gray-900">Resultados DOFA Consolidados</h1>
                    <p class="text-gray-500">{{ project.organization?.name }} — {{ project.name }}</p>
                </div>
                <a :href="route('dofa.export-pdf', project.id)"
                   class="bg-gray-800 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-700">
                    ↓ Exportar PDF
                </a>
            </div>

            <!-- MEFI / MEFE -->
            <div class="grid grid-cols-3 gap-4 mb-6">
                <div class="bg-white rounded-xl shadow p-5 text-center">
                    <div class="text-3xl font-black text-blue-600">{{ parseFloat(mefi).toFixed(2) }}</div>
                    <div class="text-sm text-gray-500 mt-1">MEFI — Factores Internos</div>
                    <div class="text-xs text-gray-400 mt-1">(1.0 – 4.0)</div>
                </div>
                <div class="bg-white rounded-xl shadow p-5 text-center">
                    <div class="text-3xl font-black text-purple-600">{{ parseFloat(mefe).toFixed(2) }}</div>
                    <div class="text-sm text-gray-500 mt-1">MEFE — Factores Externos</div>
                    <div class="text-xs text-gray-400 mt-1">(1.0 – 4.0)</div>
                </div>
                <div class="bg-green-50 border-2 border-green-400 rounded-xl shadow p-5 text-center">
                    <div class="text-xl font-black text-green-700 capitalize">{{ session.quadrant }}</div>
                    <div class="text-xs text-green-600 mt-2 leading-tight">{{ quadrant_label }}</div>
                </div>
            </div>

            <!-- Scores cruzados -->
            <div class="bg-white rounded-xl shadow p-5 mb-6">
                <h2 class="text-sm font-semibold text-gray-500 uppercase mb-4">Matriz Cruzada</h2>
                <div class="grid grid-cols-4 gap-4">
                    <div v-for="(score, key) in { 'FO (Ofensiva)': cross.score_fo, 'FA (Defensiva)': cross.score_fa, 'DO (Reorientación)': cross.score_do, 'DA (Supervivencia)': cross.score_da }"
                         :key="key"
                         :class="['rounded-lg p-3 text-center', cross.quadrant && key.toLowerCase().startsWith(cross.quadrant.substring(0,2)) ? 'bg-green-100 ring-2 ring-green-500' : 'bg-gray-50']">
                        <div class="text-2xl font-black text-gray-800">{{ parseFloat(score).toFixed(2) }}</div>
                        <div class="text-xs text-gray-500 mt-1">{{ key }}</div>
                    </div>
                </div>
            </div>

            <!-- Factores por tipo -->
            <div class="grid grid-cols-2 gap-4 mb-6">
                <div v-for="(factorList, type) in factors" :key="type" :class="['rounded-xl border p-4', typeColors[type]]">
                    <h3 class="font-semibold mb-3">{{ typeLabels[type] }}</h3>
                    <div v-for="factor in factorList" :key="factor.id" class="mb-2 text-sm">
                        <div class="flex justify-between items-start gap-2">
                            <span class="text-gray-700 flex-1">{{ factor.description }}</span>
                            <span class="font-bold text-gray-900 shrink-0">{{ parseFloat(factor.score).toFixed(2) }}</span>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Análisis IA -->
            <div v-if="session.ai_analysis" class="bg-gray-900 text-white rounded-xl 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>
                    <h3 class="font-semibold">Análisis Estratégico</h3>
                </div>
                <div class="text-gray-300 text-sm leading-relaxed whitespace-pre-line">{{ session.ai_analysis }}</div>
            </div>

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

# Vista index del DOFA
cat > resources/js/Pages/Dofa/Index.vue << 'VUE'
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue'
import { router } from '@inertiajs/vue3'

const props = defineProps({
    project: Object,
    factors: Object,
    session: Object,
    quadrant_label: String,
})

const typeLabels = { F: 'Fortalezas', D: 'Debilidades', O: 'Oportunidades', A: 'Amenazas' }
const typeColors = {
    F: 'bg-green-50 border-green-300 text-green-800',
    D: 'bg-red-50 border-red-300 text-red-800',
    O: 'bg-blue-50 border-blue-300 text-blue-800',
    A: 'bg-orange-50 border-orange-300 text-orange-800',
}

const startSession = () => {
    router.post(route('dofa.start', props.project.id), {}, {
        onSuccess: () => window.location.href = route('dofa.monitor', props.project.id)
    })
}
</script>

<template>
    <AppLayout :title="`DOFA — ${project.name}`">
        <div class="max-w-4xl mx-auto px-4 py-6">
            <div class="flex justify-between items-center mb-8">
                <div>
                    <h1 class="text-2xl font-bold text-gray-900">Módulo DOFA</h1>
                    <p class="text-gray-500">{{ project.organization?.name }} — {{ project.name }}</p>
                </div>
                <div class="flex gap-3">
                    <a v-if="session?.status === 'completed'" :href="route('dofa.results', project.id)"
                       class="bg-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-green-700">
                        Ver Resultados
                    </a>
                    <button @click="startSession"
                            class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700">
                        {{ session?.status === 'open' ? 'Continuar Sesión' : 'Iniciar Nueva Sesión' }}
                    </button>
                </div>
            </div>

            <!-- Estado de sesión -->
            <div v-if="session" class="bg-white rounded-xl shadow p-4 mb-6 flex items-center justify-between">
                <div>
                    <span class="text-sm text-gray-500">Sesión activa:</span>
                    <span :class="['ml-2 px-2 py-1 rounded-full text-xs font-bold',
                        session.status === 'completed' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800']">
                        {{ session.status === 'completed' ? 'Completada' : 'En progreso' }}
                    </span>
                </div>
                <div v-if="session.status === 'open'">
                    <a :href="route('dofa.monitor', project.id)"
                       class="text-blue-600 text-sm font-medium hover:underline">Abrir monitor →</a>
                </div>
            </div>

            <!-- Factores por tipo -->
            <div class="grid grid-cols-2 gap-4">
                <div v-for="(factorList, type) in factors" :key="type"
                     :class="['rounded-xl border-2 p-4', typeColors[type]]">
                    <h3 class="font-bold mb-3 text-lg">{{ typeLabels[type] }}</h3>
                    <div v-if="factorList?.length" class="space-y-2">
                        <div v-for="factor in factorList" :key="factor.id"
                             class="bg-white bg-opacity-70 rounded-lg p-3 text-sm text-gray-700">
                            {{ factor.description }}
                        </div>
                    </div>
                    <div v-else class="text-sm opacity-60 italic">Sin factores registrados</div>
                </div>
            </div>
        </div>
    </AppLayout>
</template>
VUE

# ── 11. PLANTILLA PDF ────────────────────────────────────────────
mkdir -p resources/views/pdf

cat > resources/views/pdf/dofa.blade.php << 'BLADE'
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
        body { font-family: Arial, sans-serif; font-size: 12px; color: #333; }
        h1 { color: #1B5E20; font-size: 20px; }
        h2 { color: #2E7D32; font-size: 14px; border-bottom: 2px solid #2E7D32; padding-bottom: 4px; }
        .header { background: #1B5E20; color: white; padding: 15px; margin-bottom: 20px; }
        .grid { display: flex; gap: 15px; margin-bottom: 15px; }
        .box { flex: 1; border: 1px solid #ddd; padding: 10px; border-radius: 6px; }
        .f { border-color: #4CAF50; background: #f1f8e9; }
        .d { border-color: #f44336; background: #fce4ec; }
        .o { border-color: #2196F3; background: #e3f2fd; }
        .a { border-color: #FF9800; background: #fff3e0; }
        .metric { text-align: center; font-size: 24px; font-weight: bold; }
        .quadrant { background: #E8F5E9; border: 2px solid #4CAF50; padding: 10px; border-radius: 6px; margin: 10px 0; }
        .ai-analysis { background: #f5f5f5; padding: 12px; border-radius: 6px; white-space: pre-line; }
    </style>
</head>
<body>
    <div class="header">
        <h1>ANÁLISIS DOFA CONSOLIDADO</h1>
        <p>{{ $project->organization->name }} — {{ $project->name }}</p>
        <p>Fecha: {{ now()->format('d/m/Y') }}</p>
    </div>

    <div class="grid">
        <div class="box" style="text-align:center">
            <div class="metric" style="color:#1565C0">{{ number_format($mefi, 2) }}</div>
            <div>MEFI — Factores Internos</div>
        </div>
        <div class="box" style="text-align:center">
            <div class="metric" style="color:#6A1B9A">{{ number_format($mefe, 2) }}</div>
            <div>MEFE — Factores Externos</div>
        </div>
    </div>

    @if($session)
    <div class="quadrant">
        <strong>Cuadrante Dominante: {{ ucfirst($session->quadrant) }}</strong><br>
        {{ $quadrant_label }}
    </div>

    <h2>Matriz Cruzada</h2>
    <div class="grid">
        <div class="box f">FO: {{ number_format($cross['score_fo'], 2) }}</div>
        <div class="box o">FA: {{ number_format($cross['score_fa'], 2) }}</div>
        <div class="box a">DO: {{ number_format($cross['score_do'], 2) }}</div>
        <div class="box d">DA: {{ number_format($cross['score_da'], 2) }}</div>
    </div>
    @endif

    @foreach(['F' => 'Fortalezas', 'D' => 'Debilidades', 'O' => 'Oportunidades', 'A' => 'Amenazas'] as $type => $label)
    @if(isset($factors[$type]) && $factors[$type]->count())
    <h2>{{ $label }}</h2>
    @foreach($factors[$type] as $factor)
    <div class="box {{ strtolower($type) }}" style="margin-bottom:6px">
        {{ $factor->description }} — Score: {{ number_format($factor->score, 2) }}
    </div>
    @endforeach
    @endif
    @endforeach

    @if($session?->ai_analysis)
    <h2>Análisis Estratégico (Claude AI)</h2>
    <div class="ai-analysis">{{ $session->ai_analysis }}</div>
    @endif
</body>
</html>
BLADE

# ── 12. COMPILAR ASSETS ──────────────────────────────────────────
echo ">>> Compilando assets Vue..."
npm run build

# ── 13. CACHEAR RUTAS Y CONFIG ───────────────────────────────────
php artisan config:clear
php artisan route:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache

echo ""
echo "========================================="
echo "  Sprint 03 completado exitosamente"
echo ""
echo "  Módulos creados:"
echo "  ✅ DofaService (MEFI, MEFE, cruce F×O)"
echo "  ✅ DofaController (index, rate, monitor,"
echo "     results, startSession, exportPdf)"
echo "  ✅ DofaAnalysisJob (Claude API)"
echo "  ✅ Events: DofaFactorRated,"
echo "     DofaSessionCompleted (WebSocket)"
echo "  ✅ 4 vistas Vue: Index, Rate, Monitor,"
echo "     Results"
echo "  ✅ Plantilla PDF"
echo "  ✅ Rutas registradas"
echo ""
echo "  Rutas disponibles:"
echo "  /projects/{id}/dofa         → Vista consultor"
echo "  /projects/{id}/dofa/rate    → Calificación móvil"
echo "  /projects/{id}/dofa/monitor → Video beam"
echo "  /projects/{id}/dofa/results → Resultados finales"
echo "========================================="
