Cambio exportación Excel

This commit is contained in:
Juan Felipe Zapata Moreno 2025-10-07 13:21:00 -06:00
parent a080e679d7
commit 7986cc3650
4 changed files with 51 additions and 247 deletions

View File

@ -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',

View File

@ -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">

View File

@ -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>

View File

@ -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']);
});