Cambio exportación Excel
This commit is contained in:
parent
a080e679d7
commit
7986cc3650
@ -143,76 +143,39 @@ private function getFullData($startDate, $endDate, $period)
|
||||
}
|
||||
|
||||
|
||||
public function getSupportOptions(Request $request)
|
||||
{
|
||||
try {
|
||||
$typeId = $request->query('type_id');
|
||||
|
||||
if (!$typeId) {
|
||||
return response()->json(['error' => 'type_id es requerido'], 400);
|
||||
}
|
||||
|
||||
$url = 'https://apoyos.comalcalco.gob.mx/beneficiaries/export/support-options';
|
||||
|
||||
$response = Http::timeout(30)->get($url, [
|
||||
'type_id' => $typeId
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
Log::error('Error al obtener opciones de apoyo', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
'type_id' => $typeId
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => 'Error al obtener opciones de apoyo'
|
||||
], $response->status());
|
||||
}
|
||||
|
||||
return response()->json($response->json());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error en getSupportOptions', [
|
||||
'exception' => $e->getMessage(),
|
||||
'type_id' => $request->query('type_id')
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => 'Error interno del servidor'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function exportExcel(Request $request)
|
||||
{
|
||||
try {
|
||||
// CAMBIO: Usar los parámetros correctos que espera la API
|
||||
// Obtener parámetros necesarios para la API
|
||||
$params = $request->only([
|
||||
'start_date',
|
||||
'end_date',
|
||||
'type_id',
|
||||
'support_id'
|
||||
'department'
|
||||
]);
|
||||
|
||||
// Validaciones básicas
|
||||
if (!$params['start_date'] || !$params['end_date']) {
|
||||
if (!isset($params['start_date']) || !isset($params['end_date'])) {
|
||||
return response()->json([
|
||||
'error' => 'Las fechas son requeridas'
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (!$params['type_id']) {
|
||||
if (!isset($params['department']) || $params['department'] === '') {
|
||||
return response()->json([
|
||||
'error' => 'El type_id es requerido'
|
||||
'error' => 'El departamento es requerido'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$url = 'https://apoyos.comalcalco.gob.mx/beneficiaries/export/excel';
|
||||
// Validar que department sea 1 o 3
|
||||
if (!in_array((int)$params['department'], [1, 3])) {
|
||||
return response()->json([
|
||||
'error' => 'El departamento debe ser Atención Ciudadana o DIF'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Log de parámetros para debug
|
||||
Log::info('Exportando Excel con parámetros:', $params);
|
||||
$url = 'https://apoyos.comalcalco.gob.mx/api/beneficiaries/export-excel';
|
||||
|
||||
// IMPORTANTE: Hacer la petición sin timeout muy alto y con withoutVerifying para SSL
|
||||
// Hacer la petición a la API externa
|
||||
$response = Http::withoutVerifying()
|
||||
->timeout(60)
|
||||
->get($url, $params);
|
||||
@ -229,24 +192,30 @@ public function exportExcel(Request $request)
|
||||
], $response->status());
|
||||
}
|
||||
|
||||
// CORREGIR: Verificar que la respuesta sea realmente un Excel
|
||||
// Verificar que la respuesta sea realmente un Excel
|
||||
$contentType = $response->header('Content-Type');
|
||||
|
||||
if (!str_contains($contentType, 'spreadsheet') && !str_contains($contentType, 'excel')) {
|
||||
if (!str_contains($contentType, 'spreadsheet') && !str_contains($contentType, 'excel') && !str_contains($contentType, 'octet-stream')) {
|
||||
Log::error('Respuesta no es un archivo Excel', [
|
||||
'url' => $url,
|
||||
'params' => $params,
|
||||
'status' => $response->status(),
|
||||
'content_type' => $contentType,
|
||||
'body_preview' => substr($response->body(), 0, 200)
|
||||
'body_preview' => substr($response->body(), 0, 500)
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => 'El servidor no devolvió un archivo Excel válido'
|
||||
'error' => 'La API externa no devolvió un archivo Excel válido. URL: ' . $url . ' | Status: ' . $response->status()
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Nombre del departamento para el archivo
|
||||
$departmentName = $params['department'] == 1 ? 'AtencionCiudadana' : 'DIF';
|
||||
|
||||
// Retornar el archivo Excel directamente con headers correctos
|
||||
return response($response->body(), 200, [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition' => 'attachment; filename="beneficiarios_' .
|
||||
'Content-Disposition' => 'attachment; filename="beneficiarios_' . $departmentName . '_' .
|
||||
$params['start_date'] . '_' . $params['end_date'] . '.xlsx"',
|
||||
'Content-Length' => strlen($response->body()),
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import axios from "axios";
|
||||
import DateRange from "@/Components/Dashboard/Form/DateRange.vue";
|
||||
|
||||
@ -10,13 +10,10 @@ const props = defineProps({
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const loading = ref(false);
|
||||
const loadingOptions = ref(false);
|
||||
const form = ref({
|
||||
startDate: new Date().toISOString().slice(0, 10),
|
||||
endDate: new Date().toISOString().slice(0, 10),
|
||||
supportType: '', // almacen o servicio
|
||||
typeId: '', // ID del tipo específico
|
||||
supportId: '' // Opciones específicas seleccionadas
|
||||
department: '' // 1 para Atención Ciudadana, 3 para DIF
|
||||
});
|
||||
|
||||
const dateRange = ref({
|
||||
@ -24,177 +21,68 @@ const dateRange = ref({
|
||||
end: new Date().toISOString().slice(0, 10)
|
||||
});
|
||||
|
||||
const supportTypes = [
|
||||
{ id: '', name: 'Seleccionar tipo de apoyo' },
|
||||
{ id: 'almacen', name: 'Almacén', api_types: [1, 2, 3, 4, 5, 6, 11, 12, 13] },
|
||||
{ id: 'servicio', name: 'Servicio', api_types: [7, 8, 9, 10, 14] }
|
||||
const departments = [
|
||||
{ id: '', name: 'Seleccionar departamento' },
|
||||
{ id: 1, name: 'Atención Ciudadana' },
|
||||
{ id: 3, name: 'DIF' }
|
||||
];
|
||||
|
||||
const typeOptions = ref([]);
|
||||
const supportOptionsData = ref([]);
|
||||
|
||||
// Watch para actualizar fechas desde DateRange
|
||||
watch(dateRange, (newRange) => {
|
||||
form.value.startDate = newRange.start;
|
||||
form.value.endDate = newRange.end;
|
||||
}, { deep: true });
|
||||
|
||||
// Watch para cargar opciones cuando cambia el tipo de apoyo
|
||||
watch(() => form.value.supportType, async (newType) => {
|
||||
if (newType && newType !== '') {
|
||||
await loadSupportOptions(newType);
|
||||
} else {
|
||||
typeOptions.value = [];
|
||||
supportOptionsData.value = [];
|
||||
form.value.typeId = '';
|
||||
form.value.supportOptions = [];
|
||||
}
|
||||
});
|
||||
|
||||
// Watch para cargar opciones específicas cuando cambia el typeId
|
||||
watch(() => form.value.typeId, async (newTypeId) => {
|
||||
if (newTypeId && newTypeId !== '') {
|
||||
await loadSpecificOptions(newTypeId);
|
||||
} else {
|
||||
supportOptionsData.value = [];
|
||||
form.value.supportOptions = [];
|
||||
}
|
||||
});
|
||||
|
||||
const loadSupportOptions = async (supportType) => {
|
||||
loadingOptions.value = true;
|
||||
try {
|
||||
const selectedType = supportTypes.find(type => type.id === supportType);
|
||||
if (selectedType && selectedType.api_types) {
|
||||
// Cargar tipos disponibles para este tipo de apoyo
|
||||
const options = selectedType.api_types.map(id => ({
|
||||
id: id,
|
||||
name: getTypeName(id)
|
||||
}));
|
||||
|
||||
typeOptions.value = [
|
||||
{ id: '', name: 'Seleccionar categoría específica' },
|
||||
...options
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cargando opciones de tipo:', error);
|
||||
} finally {
|
||||
loadingOptions.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadSpecificOptions = async (typeId) => {
|
||||
loadingOptions.value = true;
|
||||
try {
|
||||
const response = await axios.get('/api/support-options', {
|
||||
params: { type_id: typeId },
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
|
||||
if (response.data && response.data.options) {
|
||||
supportOptionsData.value = response.data.options;
|
||||
// Pre-seleccionar "Todos los enlistados" si existe
|
||||
const allOption = response.data.options.find(opt => opt.id === 'all');
|
||||
if (allOption) {
|
||||
form.value.supportOptions = ['all'];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cargando opciones específicas:', error);
|
||||
supportOptionsData.value = [];
|
||||
|
||||
// Mostrar mensaje de error más específico
|
||||
if (error.response?.data?.error) {
|
||||
alert(`Error: ${error.response.data.error}`);
|
||||
} else {
|
||||
alert('Error al cargar las opciones de apoyo');
|
||||
}
|
||||
} finally {
|
||||
loadingOptions.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeName = (id) => {
|
||||
const names = {
|
||||
1: "Medicamentos",
|
||||
2: "Material de Construcción",
|
||||
3: "Aparatos Ortopédicos",
|
||||
4: "Despensas",
|
||||
5: "Útiles Escolares",
|
||||
6: "Pañales",
|
||||
7: "Análisis Clínicos",
|
||||
8: "Traslados",
|
||||
9: "Monederos",
|
||||
10: "Apoyo social DIF",
|
||||
11: "Alimentos",
|
||||
12: "Ataúdes",
|
||||
13: "Ventiladores",
|
||||
14: "Apoyo a enfermedades de la vista"
|
||||
};
|
||||
return names[id] || `Tipo ${id}`;
|
||||
};
|
||||
|
||||
const exportToExcel = async () => {
|
||||
if (!form.value.supportType || !form.value.typeId) {
|
||||
alert('Por favor selecciona el tipo de apoyo y la categoría específica');
|
||||
if (!form.value.department) {
|
||||
alert('Por favor selecciona el departamento');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
// CAMBIO: Usar los parámetros correctos que espera la API
|
||||
const params = {
|
||||
start_date: form.value.startDate,
|
||||
end_date: form.value.endDate,
|
||||
type_id: form.value.typeId,
|
||||
support_id: form.value.supportId || 'all' // Si no hay supportId, usar 'all'
|
||||
department: form.value.department
|
||||
};
|
||||
|
||||
console.log('Enviando parámetros:', params); // Debug
|
||||
console.log('Enviando parámetros:', params);
|
||||
|
||||
// CAMBIO: Configuración más específica para descarga de archivos
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: '/api/export-excel',
|
||||
params: params,
|
||||
responseType: 'blob', // Importante para archivos binarios
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
},
|
||||
timeout: 60000 // 60 segundos timeout
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
// VERIFICAR que la respuesta sea un blob válido
|
||||
if (!response.data || response.data.size === 0) {
|
||||
throw new Error('El archivo descargado está vacío');
|
||||
}
|
||||
|
||||
// VERIFICAR el tipo de contenido
|
||||
const contentType = response.headers['content-type'] || '';
|
||||
if (!contentType.includes('spreadsheet') && !contentType.includes('excel') && !contentType.includes('octet-stream')) {
|
||||
// Si no es Excel, probablemente sea un JSON con error
|
||||
const text = await response.data.text();
|
||||
console.error('Respuesta inesperada:', text);
|
||||
throw new Error('El servidor no devolvió un archivo Excel válido');
|
||||
}
|
||||
|
||||
// Crear y descargar el archivo
|
||||
const blob = new Blob([response.data], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
|
||||
// Generar nombre de archivo único
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
|
||||
const filename = `beneficiarios_${form.value.startDate}_${form.value.endDate}_${timestamp}.xlsx`;
|
||||
const departmentName = departments.find(d => d.id === form.value.department)?.name.replace(/\s+/g, '_') || 'departamento';
|
||||
const filename = `beneficiarios_${departmentName}_${form.value.startDate}_${form.value.endDate}_${timestamp}.xlsx`;
|
||||
|
||||
// MEJORAR la descarga
|
||||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||||
// Para Internet Explorer
|
||||
window.navigator.msSaveOrOpenBlob(blob, filename);
|
||||
} else {
|
||||
// Para navegadores modernos
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
@ -204,12 +92,10 @@ const exportToExcel = async () => {
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Cerrar modal y mostrar éxito
|
||||
closeModal();
|
||||
alert('Archivo Excel descargado exitosamente');
|
||||
|
||||
@ -235,13 +121,10 @@ const exportToExcel = async () => {
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
// Resetear formulario
|
||||
form.value = {
|
||||
startDate: new Date().toISOString().slice(0, 10),
|
||||
endDate: new Date().toISOString().slice(0, 10),
|
||||
supportType: '',
|
||||
typeId: '',
|
||||
supportOptions: []
|
||||
department: ''
|
||||
};
|
||||
|
||||
dateRange.value = {
|
||||
@ -249,9 +132,6 @@ const closeModal = () => {
|
||||
end: new Date().toISOString().slice(0, 10)
|
||||
};
|
||||
|
||||
typeOptions.value = [];
|
||||
supportOptionsData.value = [];
|
||||
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
@ -299,65 +179,21 @@ const closeModal = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Apoyo -->
|
||||
<!-- Departamento -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Tipo de Apoyo
|
||||
Departamento <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="form.supportType"
|
||||
v-model="form.department"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
>
|
||||
<option v-for="type in supportTypes" :key="type.id" :value="type.id">
|
||||
{{ type.name }}
|
||||
<option v-for="dept in departments" :key="dept.id" :value="dept.id">
|
||||
{{ dept.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Categoría Específica -->
|
||||
<div v-if="form.supportType && typeOptions.length > 0">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Categoría Específica
|
||||
</label>
|
||||
<select
|
||||
v-model="form.typeId"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
:disabled="loadingOptions"
|
||||
>
|
||||
<option v-for="option in typeOptions" :key="option.id" :value="option.id">
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div v-if="loadingOptions" class="mt-2 text-sm text-blue-600">
|
||||
Cargando opciones...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opciones Específicas de Apoyo -->
|
||||
<div v-if="supportOptionsData.length > 0">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-3">
|
||||
Seleccionar Apoyos Específicos
|
||||
</label>
|
||||
<div class="max-h-64 overflow-y-auto border border-gray-200 rounded-md p-3">
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
v-for="option in supportOptionsData"
|
||||
:key="option.id"
|
||||
class="flex items-center space-x-2 text-sm"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="option.id"
|
||||
v-model="form.supportOptions"
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
/>
|
||||
<span class="flex-1">{{ option.name }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
Seleccionados: {{ form.supportOptions.length }} de {{ supportOptionsData.length }}
|
||||
Se exportarán todos los registros del departamento seleccionado
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -372,7 +208,7 @@ const closeModal = () => {
|
||||
</button>
|
||||
<button
|
||||
@click="exportToExcel"
|
||||
:disabled="loading || !form.supportType || !form.typeId"
|
||||
:disabled="loading || !form.department"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-md hover:bg-green-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors flex items-center"
|
||||
>
|
||||
<svg v-if="loading" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
||||
|
||||
@ -30,7 +30,7 @@ const notifications = ref(notificationCtl.notifications);
|
||||
// Otras variables
|
||||
const userId = router.page.props.user.id;
|
||||
|
||||
//
|
||||
//
|
||||
const darkModeStatus = ref(theme);
|
||||
|
||||
// Métodos
|
||||
@ -137,7 +137,7 @@ onMounted(()=>{
|
||||
:title="$t('users.menu')"
|
||||
class="flex items-center space-x-4 text-sm border-2 border-transparent rounded-full focus:outline-none transition"
|
||||
>
|
||||
<img
|
||||
<img
|
||||
class="h-8 w-8 rounded-full object-cover"
|
||||
:alt="$page.props.user.name"
|
||||
:src="$page.props.user.profile_photo_url"
|
||||
@ -149,9 +149,9 @@ onMounted(()=>{
|
||||
<div class="text-center block px-4 py-2 text-sm text-gray-800 border-b truncate">
|
||||
{{ $page.props.user.name }}
|
||||
</div>
|
||||
<DropdownLink :href="route('profile.show')">
|
||||
<!-- <DropdownLink :href="route('profile.show')">
|
||||
{{$t('profile')}}
|
||||
</DropdownLink>
|
||||
</DropdownLink> -->
|
||||
<DropdownLink v-if="$page.props.jetstream.hasApiFeatures" :href="route('api-tokens.index')">
|
||||
API Tokens
|
||||
</DropdownLink>
|
||||
@ -169,4 +169,4 @@ onMounted(()=>{
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
Route::inertia('/tramites', 'App/Tramites')->name('tramites');
|
||||
Route::inertia('/obras', 'App/Obras')->name('obras');
|
||||
Route::inertia('/atencion', 'App/AtencionCiudadana')->name('atencion');
|
||||
Route::get('/api/support-options', [AtencionController::class, 'getSupportOptions']);
|
||||
Route::get('/api/export-excel', [AtencionController::class, 'exportExcel']);
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user