@push('styles') @endpush
@if(!empty($isPlatformAdmin)) {{-- ════════ Scope toggle (Super Admin / System Admin) ════════ --}}
Vista de plataforma — acceso completo a todos los proyectos provisionados
@php $qs = request()->except(['scope', 'page']); @endphp Mis proyectos Todos los proyectos
@endif @php $alertDefs = [ 'grace_expired' => ['label' => 'Grace vencido', 'icon' => 'fa-bell', 'level' => 'error', 'detail' => 'Active con grace_period_end pasado'], 'mock_billing_live' => ['label' => 'Billing falso en producción','icon' => 'fa-credit-card', 'level' => 'error', 'detail' => 'Active con stripe_customer mock + amount > 0'], 'no_vps_data' => ['label' => 'Sin datos de VPS', 'icon' => 'fa-server', 'level' => 'error', 'detail' => 'Active sin server o cpanel_user'], 'unreachable' => ['label' => 'No responden HTTP', 'icon' => 'fa-link-slash', 'level' => 'error', 'detail' => 'Active pero HTTP error (timeout / 5xx)'], 'trial_ending_soon' => ['label' => 'Trial por terminar', 'icon' => 'fa-clock', 'level' => 'warn', 'detail' => 'Trial termina en ≤7 días'], 'no_airtable_sync' => ['label' => 'Sin sync con CRM', 'icon' => 'fa-database', 'level' => 'warn', 'detail' => 'Live sin airtable_record_id'], 'stripe_orphan' => ['label' => 'Stripe huérfano', 'icon' => 'fa-unlink', 'level' => 'warn', 'detail' => 'Subscription_id seteado pero tenant archived/paused'], ]; $alertsWithCount = collect($alertDefs)->map(function ($def, $key) use ($alertCounts) { $def['count'] = $alertCounts[$key] ?? 0; $def['key'] = $key; return $def; })->filter(fn($a) => $a['count'] > 0)->values(); $levelStyle = [ 'error' => ['bg' => 'rgba(241,65,108,0.08)', 'border' => '#f1416c', 'text' => '#f1416c'], 'warn' => ['bg' => 'rgba(255,168,0,0.08)', 'border' => '#ffa800', 'text' => '#b07700'], ]; @endphp @if($alertsWithCount->isNotEmpty()) {{-- ════════ Alertas accionables (M2) ════════ --}}
Necesitan atención {{ $alertsWithCount->sum('count') }} situación(es)
@if($alert) Limpiar filtro @endif
@foreach($alertsWithCount as $a) @php $sty = $levelStyle[$a['level']]; $isActive = $alert === $a['key']; $url = route('reseller.projects', array_merge(request()->except(['alert','page']), ['alert' => $a['key'], 'scope' => $scope ?? 'mine'])); @endphp @endforeach
@endif {{-- ════════════ Stats cards (counters por status) ════════════ --}} {{-- ════════════ Toolbar con filtros estratégicos ════════════ --}}
{{-- Search --}}
{{-- Tier --}}
{{-- Asesor --}} @if(isset($closers) && $closers->count() > 0)
@endif {{-- Server --}} @if(isset($servers) && $servers->count() > 0)
@endif {{-- Periodo --}}
{{-- Status hidden (se controla con los stat cards arriba) --}} @if($status) @endif {{-- Acciones --}}
@if($search || $status || $tier || $closerId || $server || $period !== 'all') Limpiar @endif Solicitudes Asesores Nuevo Proyecto
{{-- ════════════ Listado de proyectos ════════════ --}}

Proyectos {{ $projects->total() }} resultados

@if($projects->isEmpty() && !$search && !$status)

Sin proyectos provisionados

Cada vez que compres un sitio para un cliente desde el marketplace, aparece acá. Vas a poder filtrar por estado, ver el dominio y entrar al admin del sitio en 1 click.

Comprar primer proyecto-cliente
@else
@forelse($projects as $project) @php [$badgeLabel, $badgeClass] = $project->status_badge; @endphp @empty @endforelse
Proyecto Cliente Producto Asesor Estado Salud Sitio Alta Acción
{{ $project->project_name }}
@if($project->tier) {{ $project->tier }} @endif
{{ $project->user->email }}
{{ trim(($project->user->first_name ?? '') . ' ' . ($project->user->last_name ?? '')) }}
@if($project->product_name) {{ $project->product_name }} @else @endif @if($project->closer_user_id) @php $closerUser = $closers->firstWhere('id', $project->closer_user_id); @endphp @if($closerUser) {{ trim($closerUser->first_name . ' ' . $closerUser->last_name) ?: $closerUser->email }} @else #{{ $project->closer_user_id }} @endif @else @endif {{ $badgeLabel }} @if($project->isProvisioning()) @php [$pct, $etaText] = $project->provisioning_eta; @endphp
{{ $etaText }}
@endif
@php $snap = $healthSnapshots[$project->id] ?? []; $dotMap = [ 'ok' => ['bg' => '#1bc5bd', 'fa' => 'check'], 'warn' => ['bg' => '#ffa800', 'fa' => 'exclamation'], 'error' => ['bg' => '#f1416c', 'fa' => 'times'], 'skip' => ['bg' => '#d1d3e0', 'fa' => 'minus'], ]; $checkOrder = ['http' => 'HTTP', 'billing' => 'Billing', 'vps' => 'VPS', 'airtable' => 'Airtable', 'dns' => 'DNS']; @endphp
@foreach($checkOrder as $key => $label) @php $check = $snap[$key] ?? ['status' => 'skip', 'label' => '—', 'detail' => 'Sin datos']; $st = $check['status']; $cfg = $dotMap[$st] ?? $dotMap['skip']; $tip = $label . ' — ' . ($check['detail'] ?? $check['label'] ?? ''); @endphp @endforeach
@if($project->domain) {{ $project->domain }} @else @endif @if($project->created_at) {{ $project->created_at->format('d/m/Y') }}
{{ $project->created_at->diffForHumans() }}
@else — @endif
@if($project->isReachable() && $project->admin_url) Admin @elseif($project->isProvisioning()) @endif {{-- Botón eliminar — disponible para todos los estados --}}
Sin proyectos con esos filtros.
{{ $projects->links() }}
@endif
{{-- ════════ Modal Eliminar Proyecto ════════ --}} {{-- ════════ Flash messages ════════ --}} @if(session('success') || session('delete_queued') || session('delete_error')) @endif {{-- Init de tooltips Bootstrap para los health dots --}}