2026-05-07 23:31:33 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\ProfileController;
|
2026-05-25 17:21:25 +02:00
|
|
|
use App\Livewire\Reports\ReportsDashboard;
|
2026-05-07 23:31:33 +02:00
|
|
|
use App\Http\Controllers\ProjectController;
|
|
|
|
|
use App\Http\Controllers\OfflineSyncController;
|
|
|
|
|
use App\Livewire\ProjectMap;
|
|
|
|
|
use App\Livewire\ProjectList;
|
|
|
|
|
use App\Livewire\PhaseProgress;
|
2026-06-16 18:05:53 +02:00
|
|
|
use App\Livewire\PhaseGantt;
|
|
|
|
|
use App\Http\Controllers\ProjectReportController;
|
2026-05-07 23:31:33 +02:00
|
|
|
|
|
|
|
|
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
|
|
|
|
use App\Http\Controllers\Auth\ConfirmablePasswordController;
|
|
|
|
|
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
|
|
|
|
|
use App\Http\Controllers\Auth\EmailVerificationPromptController;
|
|
|
|
|
use App\Http\Controllers\Auth\NewPasswordController;
|
|
|
|
|
use App\Http\Controllers\Auth\PasswordController;
|
|
|
|
|
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
|
|
|
|
use App\Http\Controllers\Auth\RegisteredUserController;
|
|
|
|
|
use App\Http\Controllers\Auth\VerifyEmailController;
|
|
|
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Web Routes
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Redirección raíz a dashboard (solo para usuarios autenticados)
|
|
|
|
|
Route::get('/', function () {
|
|
|
|
|
return redirect()->route('dashboard');
|
|
|
|
|
})->middleware(['auth']);
|
|
|
|
|
|
|
|
|
|
// Grupo de rutas protegidas por autenticación
|
|
|
|
|
Route::middleware(['auth'])->group(function () {
|
|
|
|
|
|
|
|
|
|
// Dashboard principal (vista con estadísticas y lista de proyectos)
|
|
|
|
|
Route::get('/dashboard', function () {
|
2026-05-09 21:17:36 +02:00
|
|
|
$user = \Illuminate\Support\Facades\Auth::user();
|
2026-06-16 18:05:53 +02:00
|
|
|
$projectIds = \App\Models\Project::accessibleBy($user)->pluck('id');
|
2026-05-09 21:17:36 +02:00
|
|
|
|
|
|
|
|
$projects = \App\Models\Project::accessibleBy($user)
|
|
|
|
|
->withCount('phases')
|
2026-06-16 18:05:53 +02:00
|
|
|
->with(['phases' => fn($q) => $q->orderBy('order')])
|
|
|
|
|
->latest()->take(6)->get();
|
|
|
|
|
|
|
|
|
|
$activeProjects = \App\Models\Project::accessibleBy($user)->where('status', 'in_progress')->count();
|
|
|
|
|
$totalProjects = \App\Models\Project::accessibleBy($user)->count();
|
|
|
|
|
$totalPhases = \App\Models\Phase::whereIn('project_id', $projectIds)->count();
|
|
|
|
|
$totalFeatures = \App\Models\Feature::whereHas('layer.phase', fn($q) => $q->whereIn('project_id', $projectIds))->count();
|
|
|
|
|
$globalProgress = \App\Models\Phase::whereIn('project_id', $projectIds)->avg('progress_percent') ?? 0;
|
|
|
|
|
|
|
|
|
|
$openIssues = \App\Models\Issue::whereIn('project_id', $projectIds)->where('status', 'open')->count();
|
|
|
|
|
$criticalIssues = \App\Models\Issue::whereIn('project_id', $projectIds)->where('status', 'open')->where('priority', 'critical')->count();
|
|
|
|
|
|
|
|
|
|
$pendingInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)->where('status', 'pending')->count();
|
|
|
|
|
$completedInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)->where('status', 'completed')->count();
|
|
|
|
|
$rejectedInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)->where('status', 'rejected')->count();
|
|
|
|
|
|
|
|
|
|
$recentInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)
|
|
|
|
|
->with(['template', 'feature', 'project'])
|
|
|
|
|
->latest()->take(5)->get();
|
|
|
|
|
|
|
|
|
|
$recentIssues = \App\Models\Issue::whereIn('project_id', $projectIds)
|
|
|
|
|
->with(['feature', 'reporter', 'project'])
|
|
|
|
|
->where('status', '!=', 'closed')
|
|
|
|
|
->orderByRaw("FIELD(priority,'critical','high','medium','low')")
|
|
|
|
|
->take(5)->get();
|
|
|
|
|
|
|
|
|
|
// Projects with delay (planned_end exceeded and not completed)
|
|
|
|
|
$delayedPhases = \App\Models\Phase::whereIn('project_id', $projectIds)
|
|
|
|
|
->whereNotNull('planned_end')
|
|
|
|
|
->where('planned_end', '<', now())
|
|
|
|
|
->where('progress_percent', '<', 100)
|
|
|
|
|
->with('project')
|
|
|
|
|
->count();
|
2026-05-09 21:17:36 +02:00
|
|
|
|
|
|
|
|
return view('dashboard', [
|
|
|
|
|
'stats' => [
|
2026-06-16 18:05:53 +02:00
|
|
|
'active_projects' => $activeProjects,
|
|
|
|
|
'total_projects' => $totalProjects,
|
|
|
|
|
'total_phases' => $totalPhases,
|
|
|
|
|
'total_features' => $totalFeatures,
|
|
|
|
|
'global_progress' => round($globalProgress),
|
|
|
|
|
'open_issues' => $openIssues,
|
|
|
|
|
'critical_issues' => $criticalIssues,
|
|
|
|
|
'pending_inspections' => $pendingInspections,
|
|
|
|
|
'completed_inspections'=> $completedInspections,
|
|
|
|
|
'rejected_inspections' => $rejectedInspections,
|
|
|
|
|
'delayed_phases' => $delayedPhases,
|
2026-05-09 21:17:36 +02:00
|
|
|
],
|
2026-06-16 18:05:53 +02:00
|
|
|
'recentProjects' => $projects,
|
|
|
|
|
'recentInspections' => $recentInspections,
|
|
|
|
|
'recentIssues' => $recentIssues,
|
2026-05-09 21:17:36 +02:00
|
|
|
]);
|
2026-05-07 23:31:33 +02:00
|
|
|
})->name('dashboard');
|
2026-06-17 10:23:29 +02:00
|
|
|
Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboard');
|
|
|
|
|
Route::prefix('reports')->name('reports.')->group(function () {
|
|
|
|
|
Route::get('export/projects', [App\Http\Controllers\Reports\ExportController::class, 'exportProjects'])->name('export.projects');
|
|
|
|
|
Route::get('export/phases', [App\Http\Controllers\Reports\ExportController::class, 'exportPhases'])->name('export.phases');
|
2026-05-25 17:21:25 +02:00
|
|
|
Route::get('export/inspections', [App\Http\Controllers\Reports\ExportController::class, 'exportInspections'])->name('export.inspections');
|
|
|
|
|
});
|
2026-05-07 23:31:33 +02:00
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2026-06-16 18:05:53 +02:00
|
|
|
// Gestión de proyectos
|
2026-05-07 23:31:33 +02:00
|
|
|
// ------------------------------------------------------------
|
2026-06-16 18:05:53 +02:00
|
|
|
// Create/Edit handled by unified Livewire component
|
|
|
|
|
Route::get('/projects/create', \App\Livewire\ProjectForm::class)->name('projects.create');
|
|
|
|
|
Route::get('/projects/{project}/edit', \App\Livewire\ProjectForm::class)->name('projects.edit');
|
|
|
|
|
Route::resource('projects', ProjectController::class)->except(['create', 'edit']);
|
2026-05-07 23:31:33 +02:00
|
|
|
// Ruta personalizada para ver el mapa de un proyecto específico
|
|
|
|
|
Route::get('/projects/{project}/map', [ProjectController::class, 'map'])->name('projects.map');
|
|
|
|
|
// Ruta para que el componente Livewire muestre/gestione el progreso de una fase
|
|
|
|
|
Route::get('/phases/{phase}/progress', PhaseProgress::class)->name('phases.progress');
|
|
|
|
|
Route::get('/projects-list', ProjectList::class)->name('projects.list');
|
|
|
|
|
// Ruta para templates
|
|
|
|
|
Route::get('/projects/{project}/templates', function ($project) {
|
|
|
|
|
return view('projects.templates', ['project' => \App\Models\Project::findOrFail($project)]);
|
|
|
|
|
})->name('projects.templates')->middleware('can:edit projects');
|
|
|
|
|
|
|
|
|
|
// Rutas para el LayerManager:
|
|
|
|
|
Route::get('/projects/{project}/phases/{phase}/layers/manage', \App\Livewire\LayerManager::class)->name('layers.manage');
|
|
|
|
|
|
2026-06-16 18:05:53 +02:00
|
|
|
// Cronograma Gantt y reporte del proyecto
|
|
|
|
|
Route::get('/projects/{project}/gantt', PhaseGantt::class)->name('projects.gantt');
|
|
|
|
|
Route::get('/projects/{project}/report', [ProjectReportController::class, 'show'])->name('projects.report');
|
|
|
|
|
|
|
|
|
|
// Issues del proyecto
|
|
|
|
|
Route::get('/projects/{project}/issues', \App\Livewire\IssueManager::class)->name('projects.issues');
|
|
|
|
|
|
|
|
|
|
// Dashboard por proyecto
|
|
|
|
|
Route::get('/projects/{project}/dashboard', \App\Livewire\ProjectDashboard::class)->name('projects.dashboard');
|
|
|
|
|
|
2026-05-25 15:57:06 +02:00
|
|
|
// Cliente: portal cliente
|
|
|
|
|
Route::middleware(['auth', 'role:client'])->prefix('client')->name('client.')->group(function () {
|
|
|
|
|
Route::get('/', function () {
|
|
|
|
|
return view('client.dashboard');
|
|
|
|
|
})->name('dashboard');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-09 23:32:22 +02:00
|
|
|
// Admin: gestión de usuarios y roles
|
|
|
|
|
Route::middleware(['can:manage all'])->prefix('admin')->name('admin.')->group(function () {
|
2026-06-16 18:05:53 +02:00
|
|
|
Route::get('/users', function () { return view('admin.users'); })->name('users');
|
|
|
|
|
Route::get('/users/create', \App\Livewire\UserForm::class)->name('users.create');
|
|
|
|
|
Route::get('/users/{user}', \App\Livewire\UserView::class)->name('users.show');
|
|
|
|
|
Route::get('/users/{user}/edit', \App\Livewire\UserForm::class)->name('users.edit');
|
2026-05-09 23:32:22 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-09 22:28:20 +02:00
|
|
|
// Gestor de medios
|
|
|
|
|
Route::get('/projects/{project}/media', function (\App\Models\Project $project) {
|
|
|
|
|
return view('projects.media', compact('project'));
|
|
|
|
|
})->name('projects.media');
|
2026-06-16 18:05:53 +02:00
|
|
|
Route::get('/companies', \App\Livewire\CompanyManagement::class)->name('companies.manage');
|
|
|
|
|
Route::get('/companies/create', \App\Livewire\CompanyForm::class)->name('companies.create');
|
|
|
|
|
Route::get('/companies/{company}', \App\Livewire\CompanyView::class)->name('companies.show');
|
|
|
|
|
Route::get('/companies/{company}/edit', \App\Livewire\CompanyForm::class)->name('companies.edit');
|
2026-05-09 22:28:20 +02:00
|
|
|
|
2026-05-07 23:31:33 +02:00
|
|
|
// ------------------------------------------------------------
|
|
|
|
|
// Sincronización offline (para trabajadores en campo)
|
|
|
|
|
// ------------------------------------------------------------
|
|
|
|
|
Route::post('/offline/pending', [OfflineSyncController::class, 'storePending'])->name('offline.store');
|
|
|
|
|
Route::post('/offline/sync', [OfflineSyncController::class, 'sync'])->name('offline.sync');
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
|
|
|
// Perfil de usuario (proporcionado por Laravel Breeze)
|
|
|
|
|
// ------------------------------------------------------------
|
|
|
|
|
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile');
|
|
|
|
|
//Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
|
|
|
|
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
|
|
|
|
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
|
|
|
|
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
|
|
|
|
|
->name('logout');
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Incluir rutas de autenticación (login, registro, recuperación de contraseña, logout)
|
2026-05-25 15:57:06 +02:00
|
|
|
require __DIR__ . '/auth.php';
|