From bbe825313abd220bb563abda5999ea4dedd0eefa Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Thu, 14 Aug 2025 15:49:46 -0600 Subject: [PATCH 01/18] =?UTF-8?q?Se=20agreg=C3=B3=20las=20gr=C3=A1ficas=20?= =?UTF-8?q?a=20Tramites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 32 +- package.json | 2 + .../js/Components/Dashboard/Charts/Bars.vue | 34 ++ .../js/Components/Dashboard/Charts/Lines.vue | 32 ++ .../js/Components/Dashboard/Charts/Pie.vue | 29 + .../Dashboard/Charts/StackedBar.vue | 42 ++ .../Components/Dashboard/Form/DateRange.vue | 118 +++++ resources/js/Layouts/DashboardLayout.vue | 17 +- resources/js/Pages/Dashboard/Tramites.vue | 494 ++++++++++++++++++ routes/web.php | 83 +-- 10 files changed, 837 insertions(+), 46 deletions(-) create mode 100644 resources/js/Components/Dashboard/Charts/Bars.vue create mode 100644 resources/js/Components/Dashboard/Charts/Lines.vue create mode 100644 resources/js/Components/Dashboard/Charts/Pie.vue create mode 100644 resources/js/Components/Dashboard/Charts/StackedBar.vue create mode 100644 resources/js/Components/Dashboard/Form/DateRange.vue create mode 100644 resources/js/Pages/Dashboard/Tramites.vue diff --git a/package-lock.json b/package-lock.json index 9fbf9e7..e8c9c45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "template-laravel-vuejs", + "name": "maquetador-graficas", "lockfileVersion": 3, "requires": true, "packages": { @@ -8,12 +8,14 @@ "@inertiajs/vue3": "^1.0.0-beta.2", "@soketi/soketi": "^1.6.0", "@vueuse/core": "^9.6.0", + "chart.js": "^4.5.0", "dotenv": "^16.3.1", "express": "^4.18.2", "laravel-echo": "^1.14.2", "pusher-js": "^7.5.0", "sweetalert2": "^11.4.8", "toastr": "^2.1.4", + "vue-chartjs": "^5.3.2", "vue-i18n": "^9.2.2", "vue-multiselect": "^3.0.0-alpha.2" }, @@ -523,6 +525,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", @@ -1678,6 +1686,18 @@ "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -5451,6 +5471,16 @@ } } }, + "node_modules/vue-chartjs": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz", + "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, "node_modules/vue-i18n": { "version": "9.7.1", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.7.1.tgz", diff --git a/package.json b/package.json index cf5cb41..1778756 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,14 @@ "@inertiajs/vue3": "^1.0.0-beta.2", "@soketi/soketi": "^1.6.0", "@vueuse/core": "^9.6.0", + "chart.js": "^4.5.0", "dotenv": "^16.3.1", "express": "^4.18.2", "laravel-echo": "^1.14.2", "pusher-js": "^7.5.0", "sweetalert2": "^11.4.8", "toastr": "^2.1.4", + "vue-chartjs": "^5.3.2", "vue-i18n": "^9.2.2", "vue-multiselect": "^3.0.0-alpha.2" } diff --git a/resources/js/Components/Dashboard/Charts/Bars.vue b/resources/js/Components/Dashboard/Charts/Bars.vue new file mode 100644 index 0000000..2c4513a --- /dev/null +++ b/resources/js/Components/Dashboard/Charts/Bars.vue @@ -0,0 +1,34 @@ + + + diff --git a/resources/js/Components/Dashboard/Charts/Lines.vue b/resources/js/Components/Dashboard/Charts/Lines.vue new file mode 100644 index 0000000..9b5a718 --- /dev/null +++ b/resources/js/Components/Dashboard/Charts/Lines.vue @@ -0,0 +1,32 @@ + + + diff --git a/resources/js/Components/Dashboard/Charts/Pie.vue b/resources/js/Components/Dashboard/Charts/Pie.vue new file mode 100644 index 0000000..3d4073a --- /dev/null +++ b/resources/js/Components/Dashboard/Charts/Pie.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Components/Dashboard/Charts/StackedBar.vue b/resources/js/Components/Dashboard/Charts/StackedBar.vue new file mode 100644 index 0000000..2ebd952 --- /dev/null +++ b/resources/js/Components/Dashboard/Charts/StackedBar.vue @@ -0,0 +1,42 @@ + + + diff --git a/resources/js/Components/Dashboard/Form/DateRange.vue b/resources/js/Components/Dashboard/Form/DateRange.vue new file mode 100644 index 0000000..aace826 --- /dev/null +++ b/resources/js/Components/Dashboard/Form/DateRange.vue @@ -0,0 +1,118 @@ + + + diff --git a/resources/js/Layouts/DashboardLayout.vue b/resources/js/Layouts/DashboardLayout.vue index 0b72714..b26c1cb 100644 --- a/resources/js/Layouts/DashboardLayout.vue +++ b/resources/js/Layouts/DashboardLayout.vue @@ -35,7 +35,7 @@ onMounted(()=> { :title="title" />
- -
diff --git a/resources/js/Pages/Dashboard/Tramites.vue b/resources/js/Pages/Dashboard/Tramites.vue new file mode 100644 index 0000000..7514808 --- /dev/null +++ b/resources/js/Pages/Dashboard/Tramites.vue @@ -0,0 +1,494 @@ + + + diff --git a/routes/web.php b/routes/web.php index 2ad00e1..f4d5f1b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,86 +7,91 @@ use App\Http\Controllers\Developer\RoleController; use App\Http\Controllers\Example\IndexController as ExampleIndexController; use Illuminate\Support\Facades\Route; +use Illuminate\Support\Facades\Http; /** * Rutas generales/publicas - * + * * Rutas accesibles por todos los usuarios y no usuarios */ Route::redirect('/', '/login'); /** * Rutas del Dashboard - * + * * El dashboard es el panel de los usuarios de forma general */ Route::prefix('dashboard')->name('dashboard.')->middleware([ - 'auth:sanctum', - 'verified', - config('jetstream.auth_session') + 'auth:sanctum', + 'verified', + config('jetstream.auth_session') ])->group(function () { - Route::get('/welcome', [IndexController::class, 'index'])->name('index'); - Route::inertia('/changelogs', 'Dashboard/Changelogs')->name('changelogs'); - Route::inertia('/help', 'Dashboard/Help')->name('help'); + Route::get('/welcome', [IndexController::class, 'index'])->name('index'); + Route::inertia('/changelogs', 'Dashboard/Changelogs')->name('changelogs'); + Route::inertia('/help', 'Dashboard/Help')->name('help'); - # Log de Acciones - Route::resource('histories', HistoryLogController::class)->only([ - 'index', - 'store' - ]); + Route::inertia('/api-example', 'Dashboard/Tramites')->name('api-example'); + Route::get('/api/proxy-reporte-especial', function () { + $response = Http::get('https://tramites.comalcalco.gob.mx/reporte-especial?type=api'); + return $response->json(); + }); - Route::resource('notifications', NotificationController::class); - Route::prefix('/users')->name('users.')->group(function() - { - Route::get('/notifications', [UserController::class, 'getNotifications'])->name('notifications'); - }); + # Log de Acciones + Route::resource('histories', HistoryLogController::class)->only([ + 'index', + 'store' + ]); + + Route::resource('notifications', NotificationController::class); + Route::prefix('/users')->name('users.')->group(function () { + Route::get('/notifications', [UserController::class, 'getNotifications'])->name('notifications'); + }); }); /** * Rutas de administrador - * + * * Estas ubicaciones son del administrador, sin embargo el desarrollador * puede acceder a ellas. */ Route::prefix('admin')->name('admin.')->middleware([ - 'auth:sanctum', - config('jetstream.auth_session') + 'auth:sanctum', + config('jetstream.auth_session') ])->group(function () { - Route::resource('users', UserController::class); + Route::resource('users', UserController::class); - Route::prefix('/users')->name('users.')->group(function() - { - Route::get('{user}/settings', [UserController::class, 'settings'])->name('settings'); - Route::post('/password', [UserController::class, 'updatePassword'])->name('password'); - Route::post('/syncRoles', [UserController::class, 'syncRoles'])->name('syncRoles'); - }); + Route::prefix('/users')->name('users.')->group(function () { + Route::get('{user}/settings', [UserController::class, 'settings'])->name('settings'); + Route::post('/password', [UserController::class, 'updatePassword'])->name('password'); + Route::post('/syncRoles', [UserController::class, 'syncRoles'])->name('syncRoles'); + }); }); /** * Rutas solo del desarrollador - * + * * Son ubicaciones o funciones que pueden llegar a ser muy sensibles en el sistema, por lo que * solo el desarrollador debe de ser capaz de modificarlas o actualizarlas. */ Route::prefix('developer')->name('developer.')->middleware([ - 'auth:sanctum', - config('jetstream.auth_session') + 'auth:sanctum', + config('jetstream.auth_session') ])->group(function () { - Route::resource('roles', RoleController::class); + Route::resource('roles', RoleController::class); }); /** * Elementos de la plantilla - * + * * Estos son elementos que existen y pueden ser usados en la plantilla, vienen ejemplos de uso. - * + * * Estas rutas pueden ser comentadas o eliminadas cuando se finalice un proyecto. Por default estan ocultas * en el dashboard. */ Route::prefix('examples')->name('examples.')->middleware([ - 'auth:sanctum', - 'verified', - config('jetstream.auth_session') + 'auth:sanctum', + 'verified', + config('jetstream.auth_session') ])->group(function () { - Route::get('/', [ExampleIndexController::class, 'index'])->name('index'); -}); \ No newline at end of file + Route::get('/', [ExampleIndexController::class, 'index'])->name('index'); +}); -- 2.45.2 From 8792c5b283243d4483454c22d740a969714ddf66 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Thu, 14 Aug 2025 16:32:20 -0600 Subject: [PATCH 02/18] Se creo el archivo de obras --- .../Controllers/Dashboard/ObrasController.php | 29 ++++++++++++++++ .../Dashboard/TramiteController.php | 29 ++++++++++++++++ resources/js/Layouts/DashboardLayout.vue | 5 --- resources/js/Pages/Dashboard/Obras.vue | 29 ++++++++++++++++ resources/js/Pages/Dashboard/Tramites.vue | 33 +++++++++++-------- routes/api.php | 7 ++++ routes/web.php | 7 ++-- 7 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 app/Http/Controllers/Dashboard/ObrasController.php create mode 100644 app/Http/Controllers/Dashboard/TramiteController.php create mode 100644 resources/js/Pages/Dashboard/Obras.vue diff --git a/app/Http/Controllers/Dashboard/ObrasController.php b/app/Http/Controllers/Dashboard/ObrasController.php new file mode 100644 index 0000000..38f34bc --- /dev/null +++ b/app/Http/Controllers/Dashboard/ObrasController.php @@ -0,0 +1,29 @@ +only(['start', 'end', 'type']); + $query = array_merge(['type' => 'api'], $query); + + try { + $response = Http::timeout(5)->get('https://obras-information.comalcalco.gob.mx/api/controller.php?action=getCounters', $query); + + if (! $response->successful()) { + return response()->json(['error' => 'External service error'], $response->status()); + } + + return response()->json($response->json(), $response->status()) + ->header('Content-Type', $response->header('Content-Type', 'application/json')); + } catch (\Throwable $e) { + Log::error('Proxy error: '.$e->getMessage()); + return response()->json(['error' => 'Unable to contact external service'], 502); + } + } +} diff --git a/app/Http/Controllers/Dashboard/TramiteController.php b/app/Http/Controllers/Dashboard/TramiteController.php new file mode 100644 index 0000000..12cccf2 --- /dev/null +++ b/app/Http/Controllers/Dashboard/TramiteController.php @@ -0,0 +1,29 @@ +only(['start', 'end', 'type']); + $query = array_merge(['type' => 'api'], $query); + + try { + $response = Http::timeout(5)->get('https://tramites.comalcalco.gob.mx/reporte-especial', $query); + + if (! $response->successful()) { + return response()->json(['error' => 'External service error'], $response->status()); + } + + return response()->json($response->json(), $response->status()) + ->header('Content-Type', $response->header('Content-Type', 'application/json')); + } catch (\Throwable $e) { + Log::error('Proxy error: '.$e->getMessage()); + return response()->json(['error' => 'Unable to contact external service'], 502); + } + } +} diff --git a/resources/js/Layouts/DashboardLayout.vue b/resources/js/Layouts/DashboardLayout.vue index b26c1cb..cbacbaa 100644 --- a/resources/js/Layouts/DashboardLayout.vue +++ b/resources/js/Layouts/DashboardLayout.vue @@ -55,11 +55,6 @@ onMounted(()=> { name="help.title" to="dashboard.help" /> -
+ + +const fetchReport = async ({ start, end }) => { + // cancelar petición previa + if (cancelTokenSource) { + try { cancelTokenSource.cancel('cancel'); } catch (e) {} + } + cancelTokenSource = axios.CancelToken.source(); + + const params = {}; + if (start) params.start = start; + if (end) params.end = end; + + const res = await axios.get('/api/reporte-especial', { + params, + cancelToken: cancelTokenSource.token, + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }); + + // axios lanza en error si status >= 400, aquí devolvemos data directamente + return res.data; +}; + + + diff --git a/resources/js/Pages/Dashboard/Tramites.vue b/resources/js/Pages/Dashboard/Tramites.vue index 7514808..fa27f74 100644 --- a/resources/js/Pages/Dashboard/Tramites.vue +++ b/resources/js/Pages/Dashboard/Tramites.vue @@ -1,6 +1,7 @@ diff --git a/resources/js/Pages/Dashboard/Obras.vue b/resources/js/Pages/Dashboard/Obras.vue index 454b48d..287758b 100644 --- a/resources/js/Pages/Dashboard/Obras.vue +++ b/resources/js/Pages/Dashboard/Obras.vue @@ -1,29 +1,1282 @@ - diff --git a/routes/api.php b/routes/api.php index 4851d4c..5472386 100644 --- a/routes/api.php +++ b/routes/api.php @@ -20,7 +20,7 @@ return $request->user(); }); -Route::middleware(['throttle:60,1'])->get('/reporte-especial', [TramiteController::class, 'tramiteEspecial']); - -Route::middleware(['throttle:60,1'])->get('/reporte-obras', [ObrasController::class, 'Obras']); - +Route::middleware('api')->group(function () { + Route::get('/reporte-obras', [ObrasController::class, 'Obras'])->middleware(['throttle:60,1']); + Route::get('/reporte-especial', [TramiteController::class, 'tramiteEspecial'])->middleware(['throttle:60,1']); +}); -- 2.45.2 From f1a276cf01827776de4c7a58727c9d0b3cd0c551 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Mon, 18 Aug 2025 09:51:44 -0600 Subject: [PATCH 04/18] =?UTF-8?q?Correci=C3=B3n=20de=20la=20tabla=20Ultima?= =?UTF-8?q?s=20acciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/Pages/Dashboard/Obras.vue | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/resources/js/Pages/Dashboard/Obras.vue b/resources/js/Pages/Dashboard/Obras.vue index 287758b..8ae3c40 100644 --- a/resources/js/Pages/Dashboard/Obras.vue +++ b/resources/js/Pages/Dashboard/Obras.vue @@ -70,7 +70,6 @@ const loadDashboardData = async () => { }); const data = res.data; - console.log("Dashboard data:", data); if (!data || !data.success) { planeacionesError.value = estimacionesError.value = true; const msg = data?.error || "Error desconocido"; @@ -162,7 +161,7 @@ const loadDashboardData = async () => { : []; } - // Agregar contadores finalizadas (opcional, para uso futuro) + // Agregar contadores finalizadas if (data.counters.finalizadas) { counters.finalizadas = { count: data.counters.finalizadas.count || 0, @@ -177,7 +176,7 @@ const loadDashboardData = async () => { }; } - // Agregar contadores abiertas (opcional, para uso futuro) + // Agregar contadores abiertas if (data.counters.abiertas) { counters.abiertas = { count: data.counters.abiertas.count || 0, @@ -199,8 +198,7 @@ const loadDashboardData = async () => { id: user.supervisor_id ?? null, nombre: user.supervisor_nombre ?? "Sin nombre", correo: user.supervisor_correo ?? "Sin correo", - ultima_accion: - user.supervisor_ultima_accion ?? " ", + ultima_accion: user.supervisor_ultima_accion ?? "Sin acción reciente", fecha_accion: user.fecha_accion ?? null, detalle_accion: user.detalle_accion ?? null, proyecto_info: user.proyecto_info ?? null, @@ -1245,7 +1243,6 @@ onMounted(() => {
-
{{ supervisor.ultima_accion }}
{{ supervisor.ultima_accion }}
-- 2.45.2 From 52e8dc8153ace45b3b2d0e1be515ac4f6bdb0b9d Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Mon, 18 Aug 2025 12:27:23 -0600 Subject: [PATCH 05/18] =?UTF-8?q?Se=20agreg=C3=B3=20la=20vista=20de=20acce?= =?UTF-8?q?sos=20rapidos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/seeders/UserSeeder.php | 28 ++-- resources/js/Pages/Auth/Login.vue | 16 +- resources/js/Pages/Dashboard/Index.vue | 169 +++++++++++++++++++++- resources/js/Pages/Dashboard/Tramites.vue | 46 +++++- tailwind.config.js | 2 +- 5 files changed, 221 insertions(+), 40 deletions(-) diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index bfd99be..2e86407 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -10,15 +10,15 @@ /** * Siembra usuarios de forma segura - * + * * Siembra usuarios sin la necesidad de escribir una contraseña que quede registrada en el código. * El usuario será generado con una contraseña que puede ser consultado en el log. Esto permite * evitar el escribir las contraseñas en código. Esto permite que los usuarios de prueba sean seguros * y de proyecto en proyecto no sea una brecha de seguridad tener contraseñas dentro del código en caso * de que este pueda llegar a ser vulnerado. - * + * * @author Moisés de Jesús Cortés Castellanos - * + * * @version 1.0.0 */ class UserSeeder extends Seeder @@ -35,36 +35,28 @@ public function run() [ $developerEmail, $developerPass - ] = UserSecureSupport::new('developer@notsoweb.com'); - + ] = UserSecureSupport::new('developer@golsystems.com'); + User::create([ 'name' => 'Developer', - 'paternal' => 'Notsoweb', + 'paternal' => 'golsystems', 'email' => $developerEmail, 'phone' => '5631809090', 'password' => $developerPass, ])->assignRole('developer'); - + // Usuario administrador [ $adminEmail, $adminPass - ] = UserSecureSupport::new('admin@notsoweb.com'); - + ] = UserSecureSupport::new('admin@comalcalco.com'); + User::create([ 'name' => 'Administrador', - 'paternal' => 'Notsoweb', + 'paternal' => 'comalcalco', 'email' => $adminEmail, 'password' => $adminPass ])->assignRole('admin'); - - // Usuario de prueba - User::create([ - 'name' => 'Demo', - 'paternal' => 'Notsoweb', - 'email' => 'demo@notsoweb.com', - 'password' => Hash::make('Demo') - ]); }); } } diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue index 36ce378..1c507d9 100644 --- a/resources/js/Pages/Auth/Login.vue +++ b/resources/js/Pages/Auth/Login.vue @@ -63,7 +63,7 @@ const submit = () => { />