Se agregó las gráficas a Tramites #1

Open
juan.zapata wants to merge 18 commits from api into main
6 changed files with 370 additions and 163 deletions
Showing only changes of commit de7676da26 - Show all commits

View File

@ -4,6 +4,7 @@
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@ -11,84 +12,40 @@ class AtencionController extends BaseController
{
public function Atencion(Request $request)
{
$query = $request->only(['start', 'end', 'type', 'action']);
$query = $request->only(['start', 'end', 'type', 'action', 'period', 'charts_only']);
$input = array_merge(['action' => 'index', 'type' => 'api'], $query);
$startDate = $input['start'] ?? date('Y-01-01');
$endDate = $input['end'] ?? date('Y-12-31');
$period = $input['period'] ?? null;
$chartsOnly = $input['charts_only'] ?? false;
$baseParams = [
'start_date' => $startDate,
'end_date' => $endDate,
'type' => $input['type'] ?? 'api',
'action' => $input['action'] ?? 'index',
];
// Cache key para diferentes tipos de peticiones
$cacheKey = 'atencion_' . md5(serialize([
'start' => $startDate,
'end' => $endDate,
'period' => $period,
'charts_only' => $chartsOnly
]));
$baseUrl = 'https://apoyos.comalcalco.gob.mx/beneficiaries/stats-by-date-range';
$countsUrl = 'https://apoyos.comalcalco.gob.mx/beneficiaries/counts-by-date';
$dashboardUrl = 'https://apoyos.comalcalco.gob.mx/beneficiaries/dashboard/api';
$dashboardUrl2 = 'https://apoyos.comalcalco.gob.mx/beneficiaries/dashboard/api?type=servicio';
// Intentar obtener desde cache (2 minutos)
if (Cache::has($cacheKey)) {
return response()->json(Cache::get($cacheKey), 200)
->header('Content-Type', 'application/json');
}
try {
$response = Http::get($baseUrl, $baseParams);
if (!$response->successful()) {
return response()->json(['error' => 'Error del servicio'], $response->status());
// Si solo necesitamos gráficas, hacer menos peticiones
if ($chartsOnly) {
$result = $this->getChartsData($startDate, $endDate);
} else {
$result = $this->getFullData($startDate, $endDate, $period);
}
$mainData = $response->json();
$countsAc = [];
try {
$countsResp = Http::get($countsUrl, [
'start_date' => $startDate,
'end_date' => $endDate,
]);
// Cachear resultado por 2 minutos
Cache::put($cacheKey, $result, 120);
if ($countsResp->successful()) {
$countsAc = $countsResp->json();
} else {
Log::error('Error al obtener los conteos de atención', ['status' => $countsResp->status()]);
}
} catch (\Exception $e) {
Log::error('Error al obtener los conteos de atención', ['exception' => $e->getMessage()]);
}
$dashboardData = [];
try {
$dashboardResp = Http::get($dashboardUrl);
if ($dashboardResp->successful()) {
$dashboardData = $dashboardResp->json();
} else {
Log::error('Error al obtener datos del dashboard', ['status' => $dashboardResp->status()]);
}
} catch (\Exception $e) {
Log::error('Error al obtener datos del dashboard', ['exception' => $e->getMessage()]);
}
$dashboardServicioData = [];
try {
$dashboardResp2 = Http::get($dashboardUrl2);
if ($dashboardResp2->successful()) {
$dashboardServicioData = $dashboardResp2->json();
} else {
Log::error('Error al obtener datos del dashboard servicio', ['status' => $dashboardResp2->status()]);
}
} catch (\Exception $e) {
Log::error('Error al obtener datos del dashboard servicio', ['exception' => $e->getMessage()]);
}
// Combina todos los resultados
$combinedData = array_merge(
is_array($mainData) ? $mainData : [],
[
'counts' => $countsAc,
'dashboard' => $dashboardData,
'dashboard_servicio' => $dashboardServicioData,
]
);
return response()->json($combinedData, 200)
return response()->json($result, 200)
->header('Content-Type', 'application/json');
} catch (\Exception $e) {
Log::error('Error en la consulta de atención', ['exception' => $e->getMessage()]);
@ -96,6 +53,96 @@ public function Atencion(Request $request)
}
}
private function getChartsData($startDate, $endDate)
{
$baseParams = [
'start_date' => $startDate,
'end_date' => $endDate,
'type' => 'api',
'action' => 'index',
];
$baseUrl = 'https://apoyos.comalcalco.gob.mx/beneficiaries/stats-by-date-range';
// Solo hacer la petición principal para gráficas
$response = Http::timeout(15)->get($baseUrl, $baseParams);
if (!$response->successful()) {
throw new \Exception('Error del servicio principal');
}
return $response->json();
}
private function getFullData($startDate, $endDate, $period)
{
$baseParams = [
'start_date' => $startDate,
'end_date' => $endDate,
'type' => 'api',
'action' => 'index',
];
$urls = [
'main' => 'https://apoyos.comalcalco.gob.mx/beneficiaries/stats-by-date-range',
'counts' => 'https://apoyos.comalcalco.gob.mx/beneficiaries/counts-by-date',
'dashboard' => 'https://apoyos.comalcalco.gob.mx/beneficiaries/dashboard/api',
'dashboard_servicio' => 'https://apoyos.comalcalco.gob.mx/beneficiaries/dashboard/api?type=servicio'
];
// Hacer peticiones en paralelo usando HTTP Pool
$responses = Http::pool(fn($pool) => [
$pool->timeout(15)->get($urls['main'], $baseParams),
$pool->timeout(15)->get($urls['counts'], [
'start_date' => $startDate,
'end_date' => $endDate,
]),
$pool->timeout(15)->get($urls['dashboard']),
$pool->timeout(15)->get($urls['dashboard_servicio'])
]);
$mainData = [];
$countsAc = [];
$dashboardData = [];
$dashboardServicioData = [];
// Procesar respuestas
if ($responses[0]->successful()) {
$mainData = $responses[0]->json();
} else {
Log::error('Error en petición principal', ['status' => $responses[0]->status()]);
}
if ($responses[1]->successful()) {
$countsAc = $responses[1]->json();
} else {
Log::error('Error en petición counts', ['status' => $responses[1]->status()]);
}
if ($responses[2]->successful()) {
$dashboardData = $responses[2]->json();
} else {
Log::error('Error en petición dashboard', ['status' => $responses[2]->status()]);
}
if ($responses[3]->successful()) {
$dashboardServicioData = $responses[3]->json();
} else {
Log::error('Error en petición dashboard servicio', ['status' => $responses[3]->status()]);
}
// Combinar todos los resultados
return array_merge(
is_array($mainData) ? $mainData : [],
[
'counts' => $countsAc,
'dashboard' => $dashboardData,
'dashboard_servicio' => $dashboardServicioData,
]
);
}
public function getSupportOptions(Request $request)
{
try {

View File

@ -4,6 +4,7 @@
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class ObrasController extends BaseController
{
@ -12,10 +13,23 @@ public function Obras(Request $request)
$query = $request->only(['start', 'end', 'type', 'action']);
$query = array_merge($query, ['action' => 'index', 'type' => 'api'], $query);
// Caché por 2 minutos (datos más dinámicos)
$cacheKey = "obras_data_" . md5(serialize($query));
if ($cachedData = Cache::get($cacheKey)) {
return response()->json($cachedData, 200)
->header('Content-Type', 'application/json');
}
try {
$response = Http::timeout(5)->get('https://obras-information.comalcalco.gob.mx/api/controller.php', $query);
if (! $response->successful()) {
Log::warning('Obras service error', [
'status' => $response->status(),
'body' => $response->body(),
'query' => $query
]);
return response()->json(['error' => 'External service error'], $response->status());
}
@ -40,10 +54,16 @@ public function Obras(Request $request)
// Combinar ambas respuestas
$combinedData = array_merge($mainData, ['counters' => $countersData, 'users' => $usersData]);
// Cachear por 2 minutos
Cache::put($cacheKey, $combinedData, now()->addMinutes(2));
return response()->json($combinedData, $response->status())
->header('Content-Type', $response->header('Content-Type', 'application/json'));
} catch (\Throwable $e) {
Log::error('Proxy error: '.$e->getMessage());
Log::error('Proxy error: '.$e->getMessage(), [
'query' => $query,
'trace' => $e->getTraceAsString()
]);
return response()->json(['error' => 'Unable to contact external service'], 502);
}
}

View File

@ -1,9 +1,12 @@
<?php namespace App\Http\Controllers\Dashboard;
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class TramiteController extends BaseController
{
@ -12,17 +15,39 @@ public function tramiteEspecial(Request $request)
$query = $request->only(['start', 'end', 'type']);
$query = array_merge(['type' => 'api'], $query);
try {
$response = Http::timeout(5)->get('https://tramites.comalcalco.gob.mx/reporte-especial', $query);
// Caché por 2 minutos (datos más dinámicos)
$cacheKey = "tramites_data_" . md5(serialize($query));
if (! $response->successful()) {
if ($cachedData = Cache::get($cacheKey)) {
return response()->json($cachedData, 200)
->header('Content-Type', 'application/json');
}
try {
$response = Http::timeout(20)->get('https://tramites.comalcalco.gob.mx/reporte-especial', $query);
if (!$response->successful()) {
Log::warning('Tramites service error', [
'status' => $response->status(),
'body' => $response->body(),
'query' => $query
]);
return response()->json(['error' => 'External service error'], $response->status());
}
return response()->json($response->json(), $response->status())
$data = $response->json();
// Cachear por 2 minutos
Cache::put($cacheKey, $data, now()->addMinutes(2));
return response()->json($data, $response->status())
->header('Content-Type', $response->header('Content-Type', 'application/json'));
} catch (\Throwable $e) {
Log::error('Proxy error: '.$e->getMessage());
Log::error('Proxy error: ' . $e->getMessage(), [
'query' => $query,
'trace' => $e->getTraceAsString()
]);
return response()->json(['error' => 'Unable to contact external service'], 502);
}
}

View File

@ -67,11 +67,10 @@ const periodOptions = [
{ id: "semana", name: "Esta Semana" },
];
// NUEVO: Función para cargar datos de gráficas
//Función para cargar datos de gráficas
const loadChartsData = async () => {
const cacheKey = getChartsCacheKey();
// Verificar cache de gráficas
if (chartsCache.has(cacheKey)) {
const cachedData = chartsCache.get(cacheKey);
buildChartsFromData(cachedData);
@ -87,19 +86,16 @@ const loadChartsData = async () => {
const res = await axios.get("/api/reporte-atencion", {
params,
timeout: 10000,
timeout: 20000, // Aumentar timeout
headers: { "X-Requested-With": "XMLHttpRequest" },
});
const payload = res.data || {};
// Guardar en cache de gráficas
chartsCache.set(cacheKey, payload);
buildChartsFromData(payload);
} catch (e) {
console.error("Error cargando datos de gráficas:", e);
// En caso de error, mantener gráficas actuales o limpiar
// En caso de error, mantener gráficas actuales
}
};
@ -139,17 +135,15 @@ const normalizeResponse = (res) => {
const load = async () => {
const cacheKey = getCacheKey();
// Verificar cache primero
if (cache.has(cacheKey)) {
const cachedData = cache.get(cacheKey);
applyDataToComponents(cachedData);
applyDataToComponents(cachedData, false); // Sin gráficas
return;
}
loading.value = true;
error.value = null;
// Cancelar petición anterior
if (cancelTokenSource) {
cancelTokenSource.cancel("cancel");
}
@ -158,28 +152,22 @@ const load = async () => {
try {
const params = {
period: selectedPeriod.value,
// NO incluir fechas personalizadas aquí
};
const res = await axios.get("/api/reporte-atencion", {
params,
timeout: 10000,
timeout: 20000, // Aumentar timeout
cancelToken: cancelTokenSource.token,
headers: { "X-Requested-With": "XMLHttpRequest" },
});
const payload = res.data || {};
// Guardar en cache
cache.set(cacheKey, payload);
// Aplicar datos (SIN gráficas)
applyDataToComponents(payload, false);
applyDataToComponents(payload, false); // Sin gráficas para evitar conflicto
} catch (e) {
if (!axios.isCancel(e)) {
console.error("Error en la petición:", e);
error.value =
e.response?.data?.error || e.message || "Error al cargar datos";
error.value = e.response?.data?.error || e.message || "Error al cargar datos";
clearComponentData();
}
} finally {
@ -421,13 +409,32 @@ const closeExportModal = () => {
showExportModal.value = false;
};
const toggleGroup = (typeName) => {
if (expandedGroups.value.has(typeName)) {
expandedGroups.value.delete(typeName);
} else {
expandedGroups.value.add(typeName);
}
};
// Watchers para recargar datos cuando cambien los filtros
watch([selectedDepartment, selectedType, selectedPeriod], () => {
clearTimeout(debounceId);
debounceId = setTimeout(load, 100);
});
onMounted(load);
onMounted(() => {
// Cargar datos iniciales
load();
// Cargar gráficas iniciales
loadChartsData();
// Limpiar caches
setInterval(() => {
cache.clear();
chartsCache.clear();
}, 600000); // 10 minutos
});
</script>
<template>

View File

@ -94,7 +94,34 @@ const loadDashboardData = async () => {
}));
const lastP =
data.planeacions.lastUpdate ?? data.planeacions.last_update ?? null;
lastPlaneacion.value = lastP && (lastP.id ?? lastP.ID) ? lastP : null;
lastPlaneacion.value =
lastP && (lastP.id ?? lastP.ID)
? {
name:
lastP.name ??
lastP.user_name ??
lastP.nombre_usuario ??
"Usuario no disponible",
num_proyecto: lastP.num_proyecto ?? lastP.numProyecto ?? null,
proyecto: lastP.proyecto ?? "Proyecto no disponible",
desc_partida:
lastP.desc_partida ??
lastP.descripcion_partida ??
"Partida no disponible",
clave: lastP.clave ?? lastP.clave_concepto ?? "--",
desc_concepto:
lastP.desc_concepto ??
lastP.descripcion_concepto ??
"Concepto no disponible",
cumplimiento: lastP.cumplimiento ?? lastP.cumplimiento_total ?? 0,
desc_evidencia:
lastP.desc_evidencia ??
lastP.descripcion_evidencia ??
"Sin descripción disponible",
fecha_evidencia: lastP.fecha_evidencia ?? lastP.fecha ?? null,
created_at: lastP.created_at ?? lastP.createdAt ?? null,
}
: null;
}
// Estimaciones: normalizar y agrupar por proyecto_id
@ -143,7 +170,27 @@ const loadDashboardData = async () => {
const lastE =
data.estimacions.lastUpdate ?? data.estimacions.last_update ?? null;
lastEstimacion.value = lastE && (lastE.id ?? lastE.ID) ? lastE : null;
lastEstimacion.value =
lastE && (lastE.id ?? lastE.ID)
? {
user_name:
lastE.user_name ??
lastE.nombre_usuario ??
lastE.registrado_por ??
"Usuario no disponible",
num_estimacion:
lastE.num_estimacion ?? lastE.numEstimacion ?? null,
num_proyecto: lastE.num_proyecto ?? lastE.numProyecto ?? null,
proyecto: lastE.proyecto ?? "Proyecto no disponible",
monto_estimacion:
lastE.monto_estimacion ?? lastE.montoEstimacion ?? "0",
monto_acumulado:
lastE.monto_acumulado ?? lastE.montoAcumulado ?? "0",
periodo_estimacion:
lastE.periodo_estimacion ?? lastE.periodo ?? null,
created_at: lastE.created_at ?? lastE.createdAt ?? null,
}
: null;
}
// NUEVO: Procesar datos de contadores del segundo endpoint
@ -486,6 +533,40 @@ onMounted(() => {
</div>
</div>
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<div
class="w-8 h-8 bg-purple-100 rounded-full flex items-center justify-center"
>
<svg
class="w-4 h-4 text-indigo-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
/>
</svg>
</div>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Proyecto</p>
<p class="text-sm text-gray-600">
<span
class="font-mono text-xs bg-gray-100 px-2 py-1 rounded"
>{{ lastPlaneacion.num_proyecto || "--" }}</span
>
<span class="ml-2">{{
lastPlaneacion.proyecto || "Proyecto no disponible"
}}</span>
</p>
</div>
</div>
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<div
@ -699,6 +780,36 @@ onMounted(() => {
<div v-else class="p-6">
<div class="space-y-4">
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<div
class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center"
>
<svg
class="w-4 h-4 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</div>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">
Registrado por
</p>
<p class="text-sm text-gray-600 truncate">
{{ lastEstimacion.user_name }}
</p>
</div>
</div>
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<div

View File

@ -5,20 +5,21 @@ import axios from "axios";
import Bars from "@/Components/Dashboard/Charts/Bars.vue";
import Pie from "@/Components/Dashboard/Charts/Pie.vue";
import DateRange from "@/Components/Dashboard/Form/DateRange.vue";
import Footer from "@/Components/Dashboard/Footer.vue";
import AppLayout from "@/Layouts/AppLayout.vue";
const data = ref(null);
const loading = ref(true);
const data = ref({
procedures_opened_today: 0,
procedures_with_movements_today: 0,
procedures_by_administration: [],
movements_by_procedure: [],
});
const error = ref(null);
// Rango inicial: HoyHoy (coincide con preset del DateRange)
const today = new Date().toISOString().slice(0, 10);
const dateRange = ref({ start: today, end: today });
// toggles
const showBars = ref(false);
const showPie = ref(false);
// datasets
const barChartData = ref({ labels: [], datasets: [] });
const pieChartData = ref({ labels: [], datasets: [] });
@ -86,27 +87,31 @@ const fetchReport = async ({ start, end }) => {
headers: { "X-Requested-With": "XMLHttpRequest" },
});
// axios lanza en error si status >= 400, aquí devolvemos data directamente
return res.data;
};
const load = async () => {
loading.value = true;
error.value = null;
try {
const payload = await fetchReport({
start: dateRange.value.start,
end: dateRange.value.end,
});
data.value = payload;
mapToCharts(payload, mode.value);
data.value = {
procedures_opened_today: payload?.procedures_opened_today || 0,
procedures_with_movements_today: payload?.procedures_with_movements_today || 0,
procedures_by_administration: Array.isArray(payload?.procedures_by_administration)
? payload.procedures_by_administration : [],
movements_by_procedure: Array.isArray(payload?.movements_by_procedure)
? payload.movements_by_procedure : [],
};
mapToCharts(data.value, mode.value);
} catch (e) {
if (e.name !== "AbortError") {
if (!axios.isCancel(e)) {
console.error(e);
error.value = e.message || "Error desconocido";
mapToCharts(data.value, mode.value);
}
} finally {
loading.value = false;
}
};
@ -119,60 +124,76 @@ watch(
{ deep: true }
);
onMounted(load);
onMounted(() => {
mapToCharts(data.value, mode.value);
load();
});
// --- mapping charts: usa SIEMPRE el rango ---
function mapToCharts(api, currentMode) {
const list = Array.isArray(api?.movements_by_procedure)
const proceduresList = Array.isArray(api?.movements_by_procedure)
? api.movements_by_procedure
: [];
const labels = list.map((it) => (it.name || "").toUpperCase());
// Filtrar procedimientos con datos significativos y ordenar por total descendente
const filteredProcedures = proceduresList
.filter((proc) => (proc.opened || 0) + (proc.closed || 0) > 0)
.sort((a, b) => {
const totalA = (a.opened || 0) + (a.closed || 0);
const totalB = (b.opened || 0) + (b.closed || 0);
return totalB - totalA;
});
// Pie: total (opened + closed). Si tu API devuelve valores por rango, puedes sustituir aquí.
const pieValues = list.map((it) => (it.opened || 0) + (it.closed || 0));
const labels = filteredProcedures.map((proc) =>
(proc.name || "").toUpperCase().substring(0, 30) +
((proc.name || "").length > 30 ? "..." : "")
);
// Pie: Distribución de trámites por procedimiento
const pieValues = filteredProcedures.map((proc) => (proc.opened || 0) + (proc.closed || 0));
pieChartData.value = {
labels,
labels: labels.length > 0 ? labels : ["Sin datos"],
datasets: [
{
label: "Recaudados",
data: pieValues,
backgroundColor: labels.map(
(_, i) => `hsl(${(i * 360) / Math.max(1, labels.length)}, 70%, 50%)`
),
label: "Distribución de Trámites por Procedimiento",
data: pieValues.length > 0 ? pieValues : [0],
backgroundColor: labels.length > 0
? labels.map((_, i) => `hsl(${(i * 360) / Math.max(1, labels.length)}, 70%, 50%)`)
: ["#e5e7eb"],
},
],
};
// Bars: dataset según modo
// Bars: Trámites más solicitados según el modo
let barLabel = "";
let barValues = [];
if (currentMode === "día") {
barLabel = "Solicitados Hoy";
barValues = list.map(
(it) => it.procedures_opened_today ?? it.opened_today ?? 0
);
barLabel = "Trámites Abiertos Hoy";
barValues = filteredProcedures.map((proc) => proc.opened_today || 0);
} else if (currentMode === "semana") {
barLabel = "Solicitados Semana";
barValues = list.map((it) => it.total ?? 0);
barLabel = "Trámites de la Semana";
barValues = filteredProcedures.map((proc) => proc.week_total || (proc.opened || 0));
} else {
barLabel = "Solicitados en Rango";
barValues = list.map((it) => {
if (typeof it.range_total === "number") return it.range_total;
return (it.opened || 0) + (it.closed || 0);
barLabel = "Trámites en el Rango Seleccionado";
barValues = filteredProcedures.map((proc) => {
// Priorizar datos específicos del rango si están disponibles
if (typeof proc.range_total === "number") return proc.range_total;
return (proc.opened || 0) + (proc.closed || 0);
});
}
barChartData.value = {
labels,
datasets: [barChartOptions.makeDataset(barLabel, barValues)],
labels: labels.length > 0 ? labels : ["Sin datos"],
datasets: [
{
...barChartOptions.makeDataset(barLabel, barValues.length > 0 ? barValues : [0]),
backgroundColor: "rgba(99, 102, 241, 0.2)",
borderColor: "rgba(99, 102, 241, 1)",
},
],
};
}
// UI helpers
const toggleBars = () => (showBars.value = !showBars.value);
const togglePie = () => (showPie.value = !showPie.value);
</script>
<template>
@ -243,14 +264,6 @@ const togglePie = () => (showPie.value = !showPie.value);
v-if="loading"
class="fixed inset-0 bg-white/95 backdrop-blur-sm flex items-center justify-center z-50 transition-all duration-300"
>
<div class="text-center">
<div class="relative">
<div
class="animate-spin rounded-full h-16 w-16 border-4 border-blue-200 border-t-blue-600 mx-auto"
></div>
</div>
<p class="mt-6 text-gray-700 font-medium">Cargando datos...</p>
</div>
</div>
<div v-else>
<div class="min-h-screen bg-gray-50 py-8">
@ -264,21 +277,6 @@ const togglePie = () => (showPie.value = !showPie.value);
</h1>
<p class="text-gray-600">Resumen por unidad administrativa</p>
</div>
<!-- <div class="ml-auto flex items-center gap-3">
<button
@click="toggleBars"
class="bg-primary text-white px-4 py-2 rounded"
>
Gráfica de Barras
</button>
<button
@click="togglePie"
class="bg-primary text-white px-4 py-2 rounded"
>
Más recaudados
</button>
</div> -->
</div>
</div>
@ -487,7 +485,7 @@ const togglePie = () => (showPie.value = !showPie.value);
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-white divide-y divide-gray-200 uppercase">
<tr
v-for="procedure in data.movements_by_procedure || []"
:key="procedure.id"
@ -554,7 +552,6 @@ const togglePie = () => (showPie.value = !showPie.value);
<!-- Bars -->
<div
v-if="showBars"
class="max-w-7xl mx-auto mt-12 px-4 sm:px-6 lg:px-8 mb-8 bg-white rounded-lg shadow p-6"
>
<!-- DateRange mejorado -->
@ -565,7 +562,6 @@ const togglePie = () => (showPie.value = !showPie.value);
<!-- Pie -->
<div
v-if="showPie"
class="max-w-7xl mx-auto mt-8 px-4 sm:px-6 lg:px-8 bg-white rounded-lg shadow p-6"
>
<Pie :chartData="pieChartData" :chartOptions="pieChartOptions" />
@ -579,6 +575,7 @@ const togglePie = () => (showPie.value = !showPie.value);
</div>
</div>
</div>
<Footer />
</div>
</AppLayout>
</template>