From 52e6f5cdcef8a71c858a686223aabb1ddf031012 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Wed, 29 Oct 2025 18:43:04 -0600 Subject: [PATCH] Fix: corregir arquitectura - TODO debe pasar por Metabase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING: Violación de arquitectura corregida - Eliminar endpoint /api/postgres/query (acceso directo a DB prohibido) - Cambiar /api/clientes para usar query de Metabase en lugar de SQL directo - Crear endpoint /api/metabase/opciones-filtros para obtener opciones - Cambiar loadOpcionesFiltros para usar API en lugar de MCP directo - Usar "Informe Ingresos - Lista de Clientes con Totales" para clientes - Usar "Informe Ingresos - Opciones de Filtros" para opciones - Respetar filosofía: Metabase calcula TODO, Vue solo renderiza - La app NUNCA habla directamente con bases de datos --- nuxt4-app/app/pages/informe-ingresos.vue | 32 +-------- nuxt4-app/server/api/clientes/index.get.ts | 69 ++++++++++++++----- .../api/metabase/opciones-filtros.get.ts | 62 +++++++++++++++++ nuxt4-app/server/api/postgres/query.post.ts | 34 --------- 4 files changed, 115 insertions(+), 82 deletions(-) create mode 100644 nuxt4-app/server/api/metabase/opciones-filtros.get.ts delete mode 100644 nuxt4-app/server/api/postgres/query.post.ts diff --git a/nuxt4-app/app/pages/informe-ingresos.vue b/nuxt4-app/app/pages/informe-ingresos.vue index ff5e95b..0032fff 100644 --- a/nuxt4-app/app/pages/informe-ingresos.vue +++ b/nuxt4-app/app/pages/informe-ingresos.vue @@ -599,35 +599,9 @@ function onUpdateFechaHasta(value: string | null) { // Cargar opciones de filtros desde Metabase async function loadOpcionesFiltros() { try { - // Ejecutar la query "Informe Ingresos - Opciones de Filtros" (ID: 53) - const result = await $fetch('/__mcp/mcp__nucleodocs-metabase__metabase_execute_card', { - method: 'POST', - body: { - card_id: 53, - parameters: [] - } - }) - - if (result && result.data && result.data.rows && result.data.rows.length > 0) { - const row = result.data.rows[0] - const cols = result.data.cols - - // Transformar respuesta a objeto - const data: any = {} - cols.forEach((col: any, index: number) => { - data[col.name] = row[index] - }) - - // Parsear arrays JSON de ubicaciones, calidades, tipos, estados - opcionesFiltros.value = { - ubicaciones: data.ubicaciones ? JSON.parse(data.ubicaciones) : [], - calidades: data.calidades ? JSON.parse(data.calidades) : [], - tipos: data.tipos ? JSON.parse(data.tipos) : [], - estados: data.estados ? JSON.parse(data.estados) : [] - } - - console.log('[Informe] Opciones de filtros cargadas:', opcionesFiltros.value) - } + const result = await $fetch('/api/metabase/opciones-filtros') + opcionesFiltros.value = result + console.log('[Informe] Opciones de filtros cargadas:', opcionesFiltros.value) } catch (error) { console.error('[Informe] Error loading opciones de filtros:', error) // No fallar si no se pueden cargar las opciones, solo usar arrays vacíos diff --git a/nuxt4-app/server/api/clientes/index.get.ts b/nuxt4-app/server/api/clientes/index.get.ts index e063065..f9e5789 100644 --- a/nuxt4-app/server/api/clientes/index.get.ts +++ b/nuxt4-app/server/api/clientes/index.get.ts @@ -1,33 +1,64 @@ /** - * Get all clients from Supabase facturador database + * Get all clients from Metabase + * Uses "Informe Ingresos - Lista de Clientes con Totales" query * Returns: id, name, ubicacion for use in filters */ export default defineEventHandler(async () => { try { - // Query clientes table ordered by name - const query = ` - SELECT - id, - name, - ubicacion, - cedula, - telefono, - email - FROM clientes - ORDER BY name ASC - ` + // Get all cards to find the clientes query + const allCards = await getMetabaseCards('all') - const result = await $fetch('/api/postgres/query', { - method: 'POST', - body: { query } + // Find "Informe Ingresos - Lista de Clientes con Totales" query + const card = allCards.find((c: any) => c.name === 'Informe Ingresos - Lista de Clientes con Totales') + + if (!card) { + throw createError({ + statusCode: 404, + statusMessage: 'Clientes query not found in Metabase' + }) + } + + // Execute the query with empty filters to get all clientes + const result = await executeCardQuery(card.id, [ + { type: 'text', target: ['variable', ['template-tag', 'fecha_desde']], value: '' }, + { type: 'text', target: ['variable', ['template-tag', 'fecha_hasta']], value: '' }, + { type: 'boolean', target: ['variable', ['template-tag', 'incluir_anulados']], value: false }, + { type: 'number', target: ['variable', ['template-tag', 'cliente_ids']], value: [] }, + { type: 'text', target: ['variable', ['template-tag', 'tipos']], value: [] }, + { type: 'text', target: ['variable', ['template-tag', 'estados']], value: [] }, + { type: 'text', target: ['variable', ['template-tag', 'ubicaciones']], value: [] }, + { type: 'text', target: ['variable', ['template-tag', 'calidades']], value: [] } + ]) + + if (!result.data?.rows || !result.data?.cols) { + return [] + } + + // Transform rows to objects with only the fields we need + const cols = result.data.cols + const clientes = result.data.rows.map((row: any[]) => { + const obj: any = {} + cols.forEach((col: any, index: number) => { + obj[col.name] = row[index] + }) + + // Return only the fields needed for the selector + return { + id: obj.cliente_id, + name: obj.cliente_nombre, + ubicacion: obj.cliente_ubicacion, + cedula: obj.cliente_cedula, + // Map any other fields you need from the query result + } }) - return result + // Sort by name + return clientes.sort((a: any, b: any) => a.name.localeCompare(b.name)) } catch (error: any) { - console.error('[API] Failed to fetch clientes:', error) + console.error('[API] Failed to fetch clientes from Metabase:', error) throw createError({ statusCode: error.statusCode || 500, - statusMessage: error.statusMessage || 'Failed to fetch clientes' + statusMessage: error.statusMessage || 'Failed to fetch clientes from Metabase' }) } }) diff --git a/nuxt4-app/server/api/metabase/opciones-filtros.get.ts b/nuxt4-app/server/api/metabase/opciones-filtros.get.ts new file mode 100644 index 0000000..d3c87a4 --- /dev/null +++ b/nuxt4-app/server/api/metabase/opciones-filtros.get.ts @@ -0,0 +1,62 @@ +/** + * Get filter options from Metabase + * Uses "Informe Ingresos - Opciones de Filtros" query (ID: 53) + * Returns: ubicaciones, calidades, tipos, estados as arrays + */ +export default defineEventHandler(async () => { + try { + // Get all cards to find the opciones query + const allCards = await getMetabaseCards('all') + + // Find "Informe Ingresos - Opciones de Filtros" query + const card = allCards.find((c: any) => c.name === 'Informe Ingresos - Opciones de Filtros') + + if (!card) { + console.warn('[API] Opciones de Filtros query not found, returning empty options') + return { + ubicaciones: [], + calidades: [], + tipos: [], + estados: [] + } + } + + // Execute the query (no parameters needed) + const result = await executeCardQuery(card.id, []) + + if (!result.data?.rows?.[0] || !result.data?.cols) { + return { + ubicaciones: [], + calidades: [], + tipos: [], + estados: [] + } + } + + const row = result.data.rows[0] + const cols = result.data.cols + + // Transform to object + const data: any = {} + cols.forEach((col: any, index: number) => { + data[col.name] = row[index] + }) + + // Parse JSON arrays + return { + ubicaciones: data.ubicaciones ? JSON.parse(data.ubicaciones) : [], + calidades: data.calidades ? JSON.parse(data.calidades) : [], + tipos: data.tipos ? JSON.parse(data.tipos) : [], + estados: data.estados ? JSON.parse(data.estados) : [] + } + } catch (error: any) { + console.error('[API] Failed to fetch opciones de filtros from Metabase:', error) + // Don't fail, just return empty options + return { + ubicaciones: [], + calidades: [], + tipos: [], + estados: [] + } + } +}) diff --git a/nuxt4-app/server/api/postgres/query.post.ts b/nuxt4-app/server/api/postgres/query.post.ts deleted file mode 100644 index 2a1ca6d..0000000 --- a/nuxt4-app/server/api/postgres/query.post.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Execute a raw SQL query against Supabase/PostgreSQL - * This is a server-only endpoint for internal API use - */ -export default defineEventHandler(async (event) => { - const body = await readBody(event) - const { query, params = [] } = body - - if (!query) { - throw createError({ - statusCode: 400, - statusMessage: 'Query is required' - }) - } - - try { - // Execute query using MCP postgres tool - const result = await $fetch('/__mcp/mcp__postgres__query', { - method: 'POST', - body: { - query, - params - } - }) - - return result - } catch (error: any) { - console.error('[Postgres] Query failed:', error) - throw createError({ - statusCode: error.statusCode || 500, - statusMessage: error.statusMessage || 'Database query failed' - }) - } -})