maquetador-graficas/resources/js/Pages/App/AtencionCiudadana.vue
2025-08-22 15:43:37 -06:00

992 lines
33 KiB
Vue

<script setup>
// filepath: /var/www/maquetador-graficas/resources/js/Pages/App/AtencionCiudadana.vue
import { ref, watch, onMounted, computed } from "vue";
import axios from "axios";
import Bars from "@/Components/Dashboard/Charts/Bars.vue";
import Pie from "@/Components/Dashboard/Charts/Pie.vue";
import Lines from "@/Components/Dashboard/Charts/Lines.vue";
import Footer from "@/Components/Dashboard/Footer.vue";
import DateRange from "@/Components/Dashboard/Form/DateRange.vue";
import ExportModal from "@/Components/Dashboard/Modal/ExportModal.vue";
import AppLayout from "@/Layouts/AppLayout.vue";
const selectedDepartment = ref("");
const selectedType = ref("almacen");
const selectedPeriod = ref("hoy");
const today = new Date().toISOString().slice(0, 10);
const dateRange = ref({ start: today, end: today });
const showExportModal = ref(false);
const loading = ref(false);
const error = ref(null);
const expandedGroups = ref(new Set());
let debounceId = null;
let cancelTokenSource = null;
let chartsDebounceId = null;
const cache = new Map();
const getCacheKey = () =>
`${selectedDepartment.value}-${selectedType.value}-${selectedPeriod.value}`;
// NUEVO: Cache separado para gráficas con rango de fechas
const chartsCache = new Map();
const getChartsCacheKey = () =>
`${dateRange.value.start}-${dateRange.value.end}`;
const totals = ref({ day: null, week: null, month: null });
const beneficiariesList = ref([]); // Lista de beneficiarios
const beneficiariesByType = ref({}); // Beneficiarios agrupados por tipo
const tipoChart = ref({ labels: [], datasets: [] });
const generoChart = ref({ labels: [], datasets: [] });
const edadChart = ref({ labels: [], datasets: [] });
const chartOptions = {
responsive: true,
scales: {
x: { beginAtZero: true },
y: { beginAtZero: true },
},
};
const departmentOptions = [
{ id: "", name: "Todos los departamentos" },
{ id: "1", name: "Atención Ciudadana" },
{ id: "3", name: "DIF" },
];
const typeOptions = [
{ id: "", name: "Todos los tipos" },
{ id: "almacen", name: "Almacén" },
{ id: "servicio", name: "Servicio" },
];
const periodOptions = [
{ id: "hoy", name: "Hoy" },
{ id: "semana", name: "Esta Semana" },
];
//Función para cargar datos de gráficas
const loadChartsData = async () => {
const cacheKey = getChartsCacheKey();
if (chartsCache.has(cacheKey)) {
const cachedData = chartsCache.get(cacheKey);
buildChartsFromData(cachedData);
return;
}
try {
const params = {
start: dateRange.value.start,
end: dateRange.value.end,
charts_only: true, // Indicador para el backend
};
const res = await axios.get("/api/reporte-atencion", {
params,
timeout: 20000, // Aumentar timeout
headers: { "X-Requested-With": "XMLHttpRequest" },
});
const payload = res.data || {};
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
}
};
// NUEVO: Función para construir gráficas desde datos
const buildChartsFromData = (payload) => {
const { porTipo, porGenero, porEdad } = normalizeResponse(payload);
buildTipoChart(porTipo);
buildGeneroChart(porGenero);
buildEdadChart(porEdad);
};
// FUNCIÓN CORREGIDA: normalizar respuesta
const normalizeResponse = (res) => {
const main = res?.data ?? {};
const counts = res?.counts ?? {};
const countsData = counts?.data_ac ?? counts?.data ?? {};
const porTipo = main?.por_tipo_apoyo ?? {};
const porGenero = main?.por_genero ?? {};
const porEdad = main?.por_rango_edad ?? {};
const totalsShape = {
today: countsData?.today ?? countsData?.dia ?? 0,
week: countsData?.week ?? countsData?.semana ?? 0,
month: countsData?.month ?? countsData?.mes ?? 0,
};
return {
porGenero,
porEdad,
porTipo,
totalsShape,
};
};
const load = async () => {
const cacheKey = getCacheKey();
if (cache.has(cacheKey)) {
const cachedData = cache.get(cacheKey);
applyDataToComponents(cachedData, false); // Sin gráficas
return;
}
loading.value = true;
error.value = null;
if (cancelTokenSource) {
cancelTokenSource.cancel("cancel");
}
cancelTokenSource = axios.CancelToken.source();
try {
const params = {
period: selectedPeriod.value,
};
const res = await axios.get("/api/reporte-atencion", {
params,
timeout: 20000, // Aumentar timeout
cancelToken: cancelTokenSource.token,
headers: { "X-Requested-With": "XMLHttpRequest" },
});
const payload = res.data || {};
cache.set(cacheKey, payload);
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";
clearComponentData();
}
} finally {
loading.value = false;
cancelTokenSource = null;
}
};
const applyDataToComponents = (payload, includeCharts = true) => {
const { porTipo, porGenero, porEdad, totalsShape } =
normalizeResponse(payload);
// Actualizar totales
totals.value.day = totalsShape?.today ?? 0;
totals.value.week = totalsShape?.week ?? 0;
totals.value.month = totalsShape?.month ?? 0;
// Procesar beneficiarios
processBeneficiariesAsync(payload);
// Solo construir gráficas si se solicita (para evitar conflicto con DateRange)
if (includeCharts) {
buildTipoChart(porTipo);
buildGeneroChart(porGenero);
buildEdadChart(porEdad);
}
};
const processBeneficiariesAsync = async (payload) => {
// Usar setTimeout para no bloquear el hilo principal
setTimeout(() => {
const dashboardAlmacenData =
payload.dashboard?.data ?? payload.dashboard ?? {};
const dashboardServicioData =
payload.dashboard_servicio?.data ?? payload.dashboard_servicio ?? {};
const periodKey =
selectedPeriod.value === "hoy"
? "hoy"
: selectedPeriod.value === "semana"
? "semana"
: "mes";
const beneficiariesAlmacen = dashboardAlmacenData[periodKey] || [];
const beneficiariesServicio = dashboardServicioData[periodKey] || [];
// Combinar y filtrar datos
let beneficiariesData = [...beneficiariesAlmacen, ...beneficiariesServicio];
beneficiariesData = filterBeneficiariesByDepartment(
beneficiariesData,
selectedDepartment.value
);
beneficiariesData = filterBeneficiariesByType(
beneficiariesData,
selectedType.value
);
beneficiariesList.value = beneficiariesData;
beneficiariesByType.value = groupBeneficiariesByType(beneficiariesData);
expandedGroups.value = new Set();
}, 0);
};
const clearComponentData = () => {
tipoChart.value = { labels: [], datasets: [] };
generoChart.value = { labels: [], datasets: [] };
edadChart.value = { labels: [], datasets: [] };
totals.value = { day: null, week: null, month: null };
beneficiariesList.value = [];
beneficiariesByType.value = {};
};
watch([selectedDepartment, selectedType, selectedPeriod], () => {
clearTimeout(debounceId);
// Limpiar cache cuando cambian los filtros principales
if (selectedPeriod.value !== selectedPeriod.value) {
cache.clear();
}
// Debounce más corto para mejor UX
debounceId = setTimeout(load, 50);
});
// NUEVO: Watcher para rango de fechas (solo gráficas)
watch(
dateRange,
() => {
clearTimeout(chartsDebounceId);
chartsDebounceId = setTimeout(loadChartsData, 350);
},
{ deep: true }
);
onMounted(() => {
// Cargar datos iniciales
load();
// Cargar gráficas iniciales
loadChartsData();
// Limpiar caches cada 5 minutos
setInterval(() => {
cache.clear();
chartsCache.clear();
}, 300000);
});
const filterBeneficiariesByDepartment = (beneficiaries, departmentId) => {
if (!departmentId || departmentId === "" || !Array.isArray(beneficiaries)) {
return beneficiaries;
}
// Usar filter nativo que es más rápido
return beneficiaries.filter(
(item) =>
item.department_ek && item.department_ek.toString() === departmentId
);
};
const filterBeneficiariesByType = (beneficiaries, typeFilter) => {
if (!typeFilter || typeFilter === "" || !Array.isArray(beneficiaries)) {
return beneficiaries;
}
return beneficiaries.filter((item) => {
return typeFilter === "almacen"
? item.warehouse_id !== null
: item.service_id !== null;
});
};
const groupBeneficiariesByType = (beneficiaries) => {
if (!Array.isArray(beneficiaries) || beneficiaries.length === 0) {
return {};
}
// Usar reduce que es más eficiente para agrupación
return beneficiaries.reduce((grouped, item) => {
const typeName =
item.warehouse?.name ||
item.warehouse?.type_name ||
item.service?.name ||
"Sin categoría";
if (!grouped[typeName]) {
grouped[typeName] = [];
}
grouped[typeName].push(item);
return grouped;
}, {});
};
const buildTipoChart = (porTipo) => {
const labels = Object.keys(porTipo || {});
const values = labels.map((k) => Number(porTipo[k] ?? 0));
tipoChart.value = {
labels,
datasets: [
{
label: "Apoyos entregados",
data: values,
backgroundColor: [
"#3b82f6",
"#ef4444",
"#10b981",
"#f59e0b",
"#8b5cf6",
"#06b6d4",
"#84cc16",
"#f97316",
],
},
],
};
};
const buildGeneroChart = (porGenero) => {
const map = {
hombres: porGenero?.hombres ?? porGenero?.Hombres ?? porGenero?.Hombre ?? 0,
mujeres: porGenero?.mujeres ?? porGenero?.Mujeres ?? porGenero?.Mujer ?? 0,
};
const labels = ["Hombres", "Mujeres"];
const values = [Number(map.hombres || 0), Number(map.mujeres || 0)];
generoChart.value = {
labels,
datasets: [
{
label: "Beneficiarios por género",
data: values,
backgroundColor: ["#3b82f6", "#fb7185"],
},
],
};
};
const buildEdadChart = (porEdad) => {
const ORDER = [
"0-17",
"18-29",
"30-39",
"40-49",
"50-59",
"60-69",
"70+",
"sin_fecha",
];
const labels = ORDER.filter((k) =>
Object.prototype.hasOwnProperty.call(porEdad || {}, k)
);
const finalLabels = labels.length ? labels : Object.keys(porEdad || {});
const values = finalLabels.map((k) => Number(porEdad[k] ?? 0));
const color = "#10B981";
edadChart.value = {
labels: finalLabels,
datasets: [
{
label: "Beneficiarios por rango de edad",
data: values,
borderColor: color,
backgroundColor: "rgba(16,185,129,0.12)",
fill: true,
tension: 0.3,
pointRadius: 3,
},
],
};
};
const openExportModal = () => {
showExportModal.value = true;
};
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(() => {
// Cargar datos iniciales
load();
// Cargar gráficas iniciales
loadChartsData();
// Limpiar caches
setInterval(() => {
cache.clear();
chartsCache.clear();
}, 600000); // 10 minutos
});
</script>
<template>
<AppLayout>
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50">
<header class="relative">
<div class="bg-gradient-to-r from-gray-100 to-gray-50 py-3 shadow-sm">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between">
<span class="text-sm font-bold text-gray-800 tracking-wide"
>COMALCALCO.GOB.MX</span
>
</div>
</div>
</div>
<div
style="
background: linear-gradient(
135deg,
#621132 0%,
#7d1a42 50%,
#621132 100%
);
"
class="shadow-xl relative overflow-hidden"
>
<!-- Patrón decorativo de fondo -->
<div class="absolute inset-0 opacity-10">
<div
class="absolute top-0 left-0 w-full h-full"
style="
background-image: repeating-linear-gradient(
45deg,
transparent,
transparent 35px,
rgba(255, 255, 255, 0.1) 35px,
rgba(255, 255, 255, 0.1) 70px
);
"
></div>
</div>
<div class="container mx-auto px-4 sm:px-6 lg:px-8 relative">
<div class="flex items-center justify-between h-32">
<div class="flex items-center space-x-4">
<img
src="https://apoyos.comalcalco.gob.mx/images/logo_blanco.png"
alt="Logo Comalcalco"
class="h-20 w-auto object-contain filter drop-shadow-lg transition-transform hover:scale-105"
/>
<div class="hidden md:block">
<h1 class="text-2xl font-bold text-white">
Dashboard de Beneficiarios
</h1>
<p class="text-blue-100 text-sm">
Sistema de seguimiento de apoyos
</p>
</div>
</div>
</div>
</div>
</div>
</header>
<section class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Filtros para Lista de Beneficiarios -->
<div
class="mb-8 bg-white rounded-xl shadow-sm border border-gray-100 p-6"
>
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900">
Filtros de Lista
</h2>
<button
@click="openExportModal"
class="inline-flex items-center px-4 py-2 bg-green-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-green-700 active:bg-green-900 focus:outline-none focus:border-green-900 focus:ring ring-green-300 disabled:opacity-25 transition ease-in-out duration-150"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
></path>
</svg>
Exportar Excel
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Departamento -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>Departamento</label
>
<select
v-model="selectedDepartment"
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
>
<option
v-for="dept in departmentOptions"
:key="dept.id"
:value="dept.id"
>
{{ dept.name }}
</option>
</select>
</div>
<!-- Tipo de Apoyo -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>Tipo de Apoyo</label
>
<select
v-model="selectedType"
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
>
<option
v-for="type in typeOptions"
:key="type.id"
:value="type.id"
>
{{ type.name }}
</option>
</select>
</div>
<!-- Período -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>Período</label
>
<select
v-model="selectedPeriod"
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
>
<option
v-for="period in periodOptions"
:key="period.id"
:value="period.id"
>
{{ period.name }}
</option>
</select>
</div>
</div>
<div class="flex items-center space-x-4 mt-4">
<div v-if="error" class="flex items-center text-red-600">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"
/>
</svg>
<span class="text-sm font-medium">{{ error }}</span>
</div>
<div class="flex items-center text-gray-500 text-xs">
<span>
Departamento: {{ selectedDepartment || "Todos" }} | Tipo:
{{ selectedType || "Todos" }} | Período: {{ selectedPeriod }} |
Beneficiarios: {{ beneficiariesList.length }}
</span>
</div>
</div>
</div>
<!-- Cards de estadísticas -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-8">
<!-- Card: Entregados Hoy -->
<div class="bg-slate-500 rounded-lg shadow-lg p-6 text-white">
<div class="flex items-center justify-between">
<div>
<h3 class="text-sm font-semibold opacity-90">Entregados Hoy</h3>
<p class="text-3xl font-bold">{{ totals.day ?? "0" }}</p>
</div>
<div class="bg-white/20 rounded-full p-3">
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
</div>
<!-- Card: Esta Semana -->
<div class="bg-green-500 rounded-lg shadow-lg p-6 text-white">
<div class="flex items-center justify-between">
<div>
<h3 class="text-sm font-semibold opacity-90">Esta Semana</h3>
<p class="text-3xl font-bold">{{ totals.week ?? "0" }}</p>
</div>
<div class="bg-white/20 rounded-full p-3">
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
</div>
</div>
<!-- Lista de Beneficiarios -->
<div
class="mb-8 bg-white rounded-xl shadow-sm border border-gray-100 p-6"
>
<h2 class="text-lg font-semibold text-gray-900 mb-4">
<svg
class="w-5 h-5 inline-block mr-2 text-blue-600"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Entregados
{{
selectedPeriod === "hoy"
? "Hoy"
: selectedPeriod === "semana"
? "Esta Semana"
: "Este Mes"
}}
({{ beneficiariesList.length }})
</h2>
<!-- Lista agrupada por tipo -->
<div
v-if="Object.keys(beneficiariesByType).length > 0"
class="space-y-6"
>
<div
v-for="(items, typeName) in beneficiariesByType"
:key="typeName"
class="border rounded-lg overflow-hidden"
>
<!-- header clicable -->
<div
class="bg-gray-50 px-4 py-3 border-b border-gray-200 cursor-pointer"
@click="toggleGroup(typeName)"
>
<div class="flex items-center justify-between">
<h3
class="flex items-center text-md font-semibold text-gray-900"
>
<svg
class="w-4 h-4 mr-2 text-gray-600"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
/>
</svg>
{{ typeName }}
</h3>
<span
class="text-sm font-medium text-gray-600 bg-white px-3 py-1 rounded-full"
>
{{ items.length }}
</span>
</div>
</div>
<div
v-show="expandedGroups.has(typeName)"
class="divide-y divide-gray-100"
>
<div
v-for="item in items"
:key="item.id"
class="p-4 hover:bg-gray-50 transition-colors"
>
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-2">
<svg
class="w-4 h-4 text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
<h4 class="font-semibold text-gray-900">
{{ item.citizen?.name }} {{ item.citizen?.paternal }}
{{ item.citizen?.maternal }}
</h4>
<span
class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full"
>
{{
item.created_at
? new Date(item.created_at).toLocaleDateString()
: "Sin fecha"
}}
</span>
<!-- BADGE DEL DEPARTAMENTO -->
<span
class="text-xs px-2 py-1 rounded-full"
:class="
item.department_ek === 1
? 'bg-purple-100 text-purple-800'
: 'bg-green-100 text-green-800'
"
>
{{
item.department_ek === 1
? "Atención Ciudadana"
: "DIF"
}}
</span>
</div>
<div
class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600"
>
<div>
<p>
<span class="font-medium">CURP:</span>
{{ item.citizen?.curp || "N/A" }}
</p>
<p>
<span class="font-medium">Dirección:</span>
{{ item.citizen?.address || "N/A" }}
</p>
<p v-if="item.citizen?.locality">
<span class="font-medium">Localidad:</span>
{{ item.citizen.locality }}
</p>
</div>
<div>
<p>
<span class="font-medium">Apoyo:</span>
{{
item.warehouse?.name ||
item.service?.name ||
"N/A"
}}
</p>
<p v-if="item.quantity">
<span class="font-medium">Cantidad:</span>
{{ item.quantity }}
</p>
<p v-if="item.deceased_name">
<span class="font-medium">Fallecido:</span>
{{ item.deceased_name }}
</p>
<p v-if="item.requester_name">
<span class="font-medium">Solicitante:</span>
{{ item.requester_name }}
</p>
</div>
</div>
<div v-if="item.observations" class="mt-2">
<p class="text-sm text-gray-600">
<span class="font-medium">Observaciones:</span>
{{ item.observations }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Mensaje cuando no hay datos -->
<div v-else-if="!loading" class="text-center py-12">
<svg
class="w-12 h-12 text-gray-400 mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
<p class="text-gray-500 text-lg">No hay registros para mostrar</p>
<p class="text-gray-400 text-sm">
Ajusta los filtros para ver los beneficiarios
</p>
</div>
</div>
<!-- NUEVA SECCIÓN: Filtros para Gráficas -->
<div
class="mb-8 bg-white rounded-xl shadow-sm border border-gray-100 p-6"
>
<div class="flex items-center justify-between mb-4">
<div>
<h2 class="text-lg font-semibold text-gray-900">
Filtros de Gráficas
</h2>
<p class="text-sm text-gray-600">
Selecciona el rango de fechas para análisis gráfico
</p>
</div>
</div>
<!-- DateRange Component -->
<DateRange
v-model="dateRange"
:presets="true"
title-start="Fecha inicio"
title-end="Fecha fin"
class="mb-4"
/>
<div class="flex items-center text-gray-500 text-xs">
<span>
Rango de gráficas: {{ dateRange.start }} a {{ dateRange.end }}
</span>
</div>
</div>
<!-- Contenedor de gráficas mejorado -->
<div class="max-w-7xl mx-auto">
<!-- Gráfica principal -->
<div
class="bg-white rounded-2xl shadow-xl border border-gray-100 p-8 mb-8 transition-all duration-300 hover:shadow-2xl"
>
<div class="text-center mb-8">
<div
class="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-2xl mb-4 shadow-lg"
>
<svg
class="w-8 h-8 text-white"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"
/>
</svg>
</div>
<h2 class="text-2xl font-bold text-gray-900 mb-2">
Distribución por Tipo de Apoyo
</h2>
<p class="text-gray-600">
Análisis detallado de los tipos de apoyos otorgados
</p>
<p class="text-sm text-gray-500 mt-2">
Período: {{ dateRange.start }} - {{ dateRange.end }}
</p>
</div>
<div class="h-96">
<Bars :chartData="tipoChart" :chartOptions="chartOptions" />
</div>
</div>
<!-- Gráficas secundarias -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div
class="bg-white rounded-2xl shadow-xl border border-gray-100 p-6 transition-all duration-300 hover:shadow-2xl"
>
<div class="text-center mb-6">
<div
class="inline-flex items-center justify-center w-12 h-12 bg-gradient-to-r from-pink-500 to-rose-600 rounded-xl mb-4 shadow-lg"
>
<svg
class="w-6 h-6 text-white"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<h3 class="text-lg font-bold text-gray-900 mb-1">
Distribución por Género
</h3>
<p class="text-sm text-gray-600">Beneficiarios por género</p>
</div>
<div>
<Pie
:chartData="generoChart"
:chartOptions="{
responsive: true,
maintainAspectRatio: false,
}"
/>
</div>
</div>
<div
class="bg-white rounded-2xl shadow-xl border border-gray-100 p-6 transition-all duration-300 hover:shadow-2xl"
>
<div class="text-center mb-6">
<div
class="inline-flex items-center justify-center w-12 h-12 bg-gradient-to-r from-emerald-500 to-teal-600 rounded-xl mb-4 shadow-lg"
>
<svg
class="w-6 h-6 text-white"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
/>
</svg>
</div>
<h3 class="text-lg font-bold text-gray-900 mb-1">
Distribución por Edad
</h3>
<p class="text-sm text-gray-600">
Beneficiarios por rango etario
</p>
</div>
<div class="h-64">
<Lines
:chartData="edadChart"
:chartOptions="{
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { position: 'bottom' } },
}"
/>
</div>
</div>
</div>
</div>
</section>
<ExportModal :show="showExportModal" @close="closeExportModal" />
<Footer />
</div>
</AppLayout>
</template>