From 84cc2083a553e767d4a1524a995b5e459f094b6d Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Tue, 19 Aug 2025 08:52:58 -0600 Subject: [PATCH] Cambios --- resources/js/Components/Dashboard/Maps.vue | 2 +- resources/js/Pages/Auth/Login.vue | 4 +- .../js/Pages/Dashboard/AtencionCiudadana.vue | 278 ++++++++++++++++++ resources/js/Pages/Dashboard/Index.vue | 30 +- resources/js/Pages/Dashboard/Obras.vue | 1 - resources/js/Pages/Dashboard/Tramites.vue | 4 +- routes/web.php | 1 + 7 files changed, 301 insertions(+), 19 deletions(-) create mode 100644 resources/js/Pages/Dashboard/AtencionCiudadana.vue diff --git a/resources/js/Components/Dashboard/Maps.vue b/resources/js/Components/Dashboard/Maps.vue index 022a954..39b88c6 100644 --- a/resources/js/Components/Dashboard/Maps.vue +++ b/resources/js/Components/Dashboard/Maps.vue @@ -144,7 +144,7 @@ const COMALCALCO_CENTER = { // Límites aproximados de Comalcalco const COMALCALCO_BOUNDS = { - north: 18.35, + north: 19.35, south: 18.18, east: -93.10, west: -93.30 diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue index 1c507d9..0b82c48 100644 --- a/resources/js/Pages/Auth/Login.vue +++ b/resources/js/Pages/Auth/Login.vue @@ -35,7 +35,7 @@ const submit = () => { @@ -75,7 +75,7 @@ const submit = () => {
+import { ref, watch, onMounted, computed } from "vue"; +import axios from "axios"; +import DateRange from "@/Components/Dashboard/Form/DateRange.vue"; +import Bars from "@/Components/Dashboard/Charts/Bars.vue"; +import { data } from "autoprefixer"; + +const dateRange = ref({ + start: new Date().toISOString().slice(0, 10), + end: new Date().toISOString().slice(0, 10), +}); + +const loading = ref(false); +const error = ref(null); +const raw = ref([]); + +// Toggle para usar datos falsos +const USE_MOCK = ref(false); + +// Tipos de apoyo (inventados) +const supportTypes = [ + "Despensas", + "Despensas Funerarias", + "Laboratorio", + "Rayos X", + "USG", +]; + +// Distribución por defecto para mock (suman 1) +const mockDistribution = [0.35, 0.08, 0.2, 0.22, 0.15]; + +const buildMockCounts = (total) => { + const counts = supportTypes.map((_, i) => + Math.round(total * (mockDistribution[i] || 0)) + ); + // Ajustar diferencia por redondeo + const diff = total - counts.reduce((s, v) => s + v, 0); + if (diff !== 0) counts[0] += diff; + return counts; +}; + +// helpers para sumar por rango +const isoDate = (d) => new Date(d + "T00:00:00"); +const totalForRange = (startIso, endIso) => { + const s = isoDate(startIso); + const e = isoDate(endIso); + return raw.value.reduce((acc, r) => { + const rd = isoDate(r.date); + if (rd >= s && rd <= e) acc += Number(r.available || 0); + return acc; + }, 0); +}; + +const dayStart = (iso) => iso; // ya vienen en formato YYYY-MM-DD +const weekStartFrom = (endIso) => { + const d = new Date(endIso + "T00:00:00"); + d.setDate(d.getDate() - 6); // 7 días inclusive + return d.toISOString().slice(0, 10); +}; +const monthRangeFrom = (endIso) => { + const d = new Date(endIso + "T00:00:00"); + d.setDate(1); + return d.toISOString().slice(0, 10); +}; + +// Construye chartData ahora por tipos de apoyo +const chartData = computed(() => { + const colors = ["#ef4444", "#3b82f6", "#f59e0b"]; + + if(USE_MOCK.value) { + const todayCounts = buildMockCounts(20); + const weekCounts = buildMockCounts(100); + const monthCounts = buildMockCounts(400); + return { + labels: supportTypes, + datasets: [ + { label: "Hoy", data: todayCounts, backgroundColor: colors[0] }, + { label: "Semana", data: weekCounts, backgroundColor: colors[1] }, + { label: "Mes", data: monthCounts, backgroundColor: colors[2] }, + ], + } + } + const end = dateRange.value.end; + const todayTotal = totalForRange(end, end); + const weekTotal = totalForRange(end, weekStartFrom(end)); + const monthTotal = totalForRange(end, monthRangeFrom(end)); + + const todayCounts = buildMockCounts(todayTotal || 0); + const weekCounts = buildMockCounts(weekTotal || 0); + const monthCounts = buildMockCounts(monthTotal || 0); + + return { + labels: supportTypes, + datasets: [ + { label: "Hoy", data: todayCounts, backgroundColor: colors[0] }, + { label: "Semana", data: weekCounts, backgroundColor: colors[1] }, + { label: "Mes", data: monthCounts, backgroundColor: colors[2] }, + ], + } +}); + + +const chartOptions = { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { stacked: false }, + y: { beginAtZero: true }, + }, +}; + +const buildEmptyRange = (start, end) => { + const s = new Date(start); + const e = new Date(end); + const list = []; + for (let d = new Date(s); d <= e; d.setDate(d.getDate() + 1)) { + list.push({ + date: new Date(d).toISOString().slice(0, 10), + available: 0, + unavailable: 0, + }); + } + return list; +}; + +const mapApiToSeries = (items, start, end) => { + const base = buildEmptyRange(start, end); + const byDate = {}; + (items || []).forEach((it) => { + const key = (it.date || it.fecha || it.day || "").slice(0, 10); + if (key) + byDate[key] = { + date: key, + available: Number( + it.available ?? it.disponibles ?? it.available_count ?? 0 + ), + }; + }); + return base.map((b) => byDate[b.date] || b); +}; + +const loadData = async () => { + loading.value = true; + error.value = null; + try { + const res = await axios.get("/api/atencion-ciudadana", { + params: { + start: dateRange.value.start, + end: dateRange.value.end, + }, + headers: { "X-Requested-With": "XMLHttpRequest" }, + }); + const payload = res.data; + const items = payload?.data ?? payload?.items ?? payload ?? []; + raw.value = mapApiToSeries( + Array.isArray(items) ? items : [], + dateRange.value.start, + dateRange.value.end + ); + } catch (e) { + console.error(e); + error.value = + e.response?.data?.message || e.message || "Error al cargar datos"; + raw.value = mapApiToSeries([], dateRange.value.start, dateRange.value.end); + } finally { + loading.value = false; + } +}; + +watch(dateRange, loadData, { deep: true }); +onMounted(loadData); + + + diff --git a/resources/js/Pages/Dashboard/Index.vue b/resources/js/Pages/Dashboard/Index.vue index 459fbfc..9f348df 100644 --- a/resources/js/Pages/Dashboard/Index.vue +++ b/resources/js/Pages/Dashboard/Index.vue @@ -1,17 +1,21 @@ @@ -19,13 +23,13 @@ const logout = () => {
-
-
- -
-
+
{ } } catch (err) { console.error(err); - // si la petición fue cancelada no sobreescribimos el error de UI if (axios.isCancel && axios.isCancel(err)) return; planeacionesError.value = estimacionesError.value = true; planeacionesErrorMessage.value = estimacionesErrorMessage.value = diff --git a/resources/js/Pages/Dashboard/Tramites.vue b/resources/js/Pages/Dashboard/Tramites.vue index 4770190..cc8394c 100644 --- a/resources/js/Pages/Dashboard/Tramites.vue +++ b/resources/js/Pages/Dashboard/Tramites.vue @@ -221,7 +221,7 @@ const togglePie = () => (showPie.value = !showPie.value);

Resumen por unidad administrativa

-
+
diff --git a/routes/web.php b/routes/web.php index b8e80e4..1d7370a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -32,6 +32,7 @@ Route::inertia('/api-tramite', 'Dashboard/Tramites')->name('api-tramite'); Route::inertia('/api-obra', 'Dashboard/Obras')->name('api-obra'); + Route::inertia('/api-atencion', 'Dashboard/AtencionCiudadana')->name('api-atencion'); # Log de Acciones Route::resource('histories', HistoryLogController::class)->only([