diff --git a/nuxt4-app/server/api/metabase/informe.post.ts b/nuxt4-app/server/api/metabase/informe.post.ts new file mode 100644 index 0000000..7e4d51c --- /dev/null +++ b/nuxt4-app/server/api/metabase/informe.post.ts @@ -0,0 +1,183 @@ +import { METABASE_QUERIES } from '~/server/config/metabase-queries' + +/** + * Execute all informe queries in parallel + * Returns data for the Informe de Ingresos page + */ +export default defineEventHandler(async (event) => { + const body = await readBody(event) + + const { + fecha_desde = null, + fecha_hasta = null, + incluir_anulados = false, + cliente_ids = [], + tipos = [], + estados = [], + ubicaciones = [], + calidades = [], + granularidad = 'dia' + } = body + + try { + // First, get all cards to find our informe queries + const allCards = await getMetabaseCards('all') + + // Find our informe queries by name using centralized config + const queryNames = METABASE_QUERIES.informe + + const cards: Record = {} + + for (const [key, name] of Object.entries(queryNames)) { + const card = allCards.find((c: any) => c.name === name) + if (!card) { + console.warn(`[Informe] Query not found: ${name}`) + } else { + cards[key] = card + } + } + + // Build parameters array for Metabase queries + const buildParameters = (includeGranularidad: boolean = false) => { + const params = [ + { + type: 'date/single', + target: ['variable', ['template-tag', 'fecha_desde']], + value: fecha_desde + }, + { + type: 'date/single', + target: ['variable', ['template-tag', 'fecha_hasta']], + value: fecha_hasta + }, + { + type: 'category', + target: ['variable', ['template-tag', 'incluir_anulados']], + value: incluir_anulados + }, + { + type: 'number', + target: ['variable', ['template-tag', 'cliente_ids']], + value: cliente_ids + }, + { + type: 'category', + target: ['variable', ['template-tag', 'tipos']], + value: tipos + }, + { + type: 'category', + target: ['variable', ['template-tag', 'estados']], + value: estados + }, + { + type: 'category', + target: ['variable', ['template-tag', 'ubicaciones']], + value: ubicaciones + }, + { + type: 'category', + target: ['variable', ['template-tag', 'calidades']], + value: calidades + } + ] + + if (includeGranularidad) { + params.push({ + type: 'category', + target: ['variable', ['template-tag', 'granularidad']], + value: granularidad + }) + } + + return params + } + + const standardParams = buildParameters(false) + const serieTemporalParams = buildParameters(true) + + // Execute all queries in parallel with error handling + const executeWithErrorHandling = async (name: string, cardId: number | undefined, parameters: any[], defaultValue: any) => { + if (!cardId) { + console.warn(`[Informe] No card ID for ${name}`) + return defaultValue + } + + try { + console.log(`[Informe] Executing query: ${name} (ID: ${cardId})`) + const result = await executeCardQuery(cardId, parameters) + console.log(`[Informe] Query ${name} returned ${result.data?.rows?.length || 0} rows`) + return result + } catch (error: any) { + console.error(`[Informe] Error executing ${name}:`, error.message) + return defaultValue + } + } + + const [ + totalesIngresoCompra, + totalesMonetarios, + totalesVerde, + listaIngresos, + listaClientes, + serieTemporal, + opcionesFiltros, + contadores + ] = await Promise.all([ + executeWithErrorHandling('totales_ingreso_compra', cards.totales_ingreso_compra?.id, standardParams, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('totales_monetarios', cards.totales_monetarios?.id, standardParams, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('totales_verde', cards.totales_verde?.id, standardParams, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('lista_ingresos', cards.lista_ingresos?.id, standardParams, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('lista_clientes', cards.lista_clientes?.id, standardParams, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('serie_temporal', cards.serie_temporal?.id, serieTemporalParams, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('opciones_filtros', cards.opciones_filtros?.id, [], { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('contadores', cards.contadores?.id, standardParams, { data: { rows: [[]], cols: [] } }) + ]) + + // Transform Metabase responses to objects for easier frontend consumption + const transformSingleRow = (result: any) => { + if (!result.data?.rows?.[0] || !result.data?.cols) return {} + + const row = result.data.rows[0] + const cols = result.data.cols + const obj: any = {} + + cols.forEach((col: any, index: number) => { + obj[col.name] = row[index] + }) + + return obj + } + + const transformMultipleRows = (result: any) => { + if (!result.data?.rows || !result.data?.cols) return [] + + const cols = result.data.cols + return result.data.rows.map((row: any[]) => { + const obj: any = {} + cols.forEach((col: any, index: number) => { + obj[col.name] = row[index] + }) + return obj + }) + } + + // Return all data in a structured format + return { + totalesIngresoCompra: transformSingleRow(totalesIngresoCompra), + totalesMonetarios: transformSingleRow(totalesMonetarios), + totalesVerde: transformSingleRow(totalesVerde), + listaIngresos: transformMultipleRows(listaIngresos), + listaClientes: transformMultipleRows(listaClientes), + serieTemporal: transformMultipleRows(serieTemporal), + opcionesFiltros: transformSingleRow(opcionesFiltros), + contadores: transformSingleRow(contadores) + } + } catch (error: any) { + console.error('[API] Failed to execute informe queries:', error) + throw createError({ + statusCode: error.statusCode || 500, + statusMessage: error.statusMessage || 'Failed to execute informe queries' + }) + } +}) diff --git a/nuxt4-app/server/api/metabase/panorama.post.ts b/nuxt4-app/server/api/metabase/panorama.post.ts index 2cd61ed..3390d93 100644 --- a/nuxt4-app/server/api/metabase/panorama.post.ts +++ b/nuxt4-app/server/api/metabase/panorama.post.ts @@ -1,3 +1,5 @@ +import { METABASE_QUERIES } from '~/server/config/metabase-queries' + /** * Execute all panorama queries in parallel * Returns data for the Panorama Facturador page @@ -11,27 +13,17 @@ export default defineEventHandler(async (event) => { // First, get all cards to find our panorama queries const allCards = await getMetabaseCards('all') - // Find our panorama queries by name - const queryNames = [ - 'panorama_totales_financieros_principales', - 'panorama_totales_ingreso_compra', - 'panorama_totales_monetarios', - 'panorama_totales_verde', - 'panorama_secos_vendidos', - 'panorama_rechazos_subproductos', - 'panorama_serie_temporal_diaria', - 'panorama_top_clientes', - 'panorama_conteo_registros' - ] + // Find our panorama queries by name using centralized config + const queryNames = METABASE_QUERIES.panorama const cards: Record = {} - for (const name of queryNames) { + for (const [key, name] of Object.entries(queryNames)) { const card = allCards.find((c: any) => c.name === name) if (!card) { console.warn(`[Panorama] Query not found: ${name}`) } else { - cards[name] = card + cards[key] = card } } @@ -84,15 +76,15 @@ export default defineEventHandler(async (event) => { topClientes, conteos ] = await Promise.all([ - executeWithErrorHandling('financieros', cards['panorama_totales_financieros_principales']?.id, { data: { rows: [[0, 0, 0]], cols: [] } }), - executeWithErrorHandling('ingresoCompra', cards['panorama_totales_ingreso_compra']?.id, { data: { rows: [[]], cols: [] } }), - executeWithErrorHandling('monetarios', cards['panorama_totales_monetarios']?.id, { data: { rows: [[]], cols: [] } }), - executeWithErrorHandling('verde', cards['panorama_totales_verde']?.id, { data: { rows: [[]], cols: [] } }), - executeWithErrorHandling('secosVendidos', cards['panorama_secos_vendidos']?.id, { data: { rows: [[]], cols: [] } }), - executeWithErrorHandling('rechazos', cards['panorama_rechazos_subproductos']?.id, { data: { rows: [], cols: [] } }), - executeWithErrorHandling('serieTemporal', cards['panorama_serie_temporal_diaria']?.id, { data: { rows: [], cols: [] } }), - executeWithErrorHandling('topClientes', cards['panorama_top_clientes']?.id, { data: { rows: [], cols: [] } }), - executeWithErrorHandling('conteos', cards['panorama_conteo_registros']?.id, { data: { rows: [[0, 0, 0, 0]], cols: [] } }) + executeWithErrorHandling('financieros', cards.totales_financieros_principales?.id, { data: { rows: [[0, 0, 0]], cols: [] } }), + executeWithErrorHandling('ingresoCompra', cards.totales_ingreso_compra?.id, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('monetarios', cards.totales_monetarios?.id, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('verde', cards.totales_verde?.id, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('secosVendidos', cards.secos_vendidos?.id, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('rechazos', cards.rechazos_subproductos?.id, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('serieTemporal', cards.serie_temporal_diaria?.id, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('topClientes', cards.top_clientes?.id, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('conteos', cards.conteo_registros?.id, { data: { rows: [[0, 0, 0, 0]], cols: [] } }) ]) // Transform Metabase responses to objects for easier frontend consumption diff --git a/nuxt4-app/server/config/metabase-queries.ts b/nuxt4-app/server/config/metabase-queries.ts new file mode 100644 index 0000000..caf6b7e --- /dev/null +++ b/nuxt4-app/server/config/metabase-queries.ts @@ -0,0 +1,44 @@ +/** + * Metabase Query Names Configuration + * + * Centraliza los nombres de las queries de Metabase para facilitar mantenimiento. + * Los nombres deben coincidir EXACTAMENTE con los nombres en Metabase. + */ + +export const METABASE_QUERIES = { + /** + * Queries para Panorama Facturador + */ + panorama: { + totales_financieros_principales: 'panorama_totales_financieros_principales', + totales_ingreso_compra: 'panorama_totales_ingreso_compra', + totales_monetarios: 'panorama_totales_monetarios', + totales_verde: 'panorama_totales_verde', + secos_vendidos: 'panorama_secos_vendidos', + rechazos_subproductos: 'panorama_rechazos_subproductos', + serie_temporal_diaria: 'panorama_serie_temporal_diaria', + top_clientes: 'panorama_top_clientes', + conteo_registros: 'panorama_conteo_registros' + }, + + /** + * Queries para Informe de Ingresos + */ + informe: { + totales_ingreso_compra: 'Informe Ingresos - Totales Ingreso y Compra', + totales_monetarios: 'Informe Ingresos - Totales Monetarios', + totales_verde: 'Informe Ingresos - Totales Verde', + lista_ingresos: 'Informe Ingresos - Lista de Ingresos', + lista_clientes: 'Informe Ingresos - Lista de Clientes con Totales', + serie_temporal: 'Informe Ingresos - Serie Temporal Acumulada', + opciones_filtros: 'Informe Ingresos - Opciones de Filtros', + contadores: 'Informe Ingresos - Contadores de Filtros' + } +} as const + +/** + * Type helper para acceder a las queries de forma type-safe + */ +export type MetabaseQueryCategory = keyof typeof METABASE_QUERIES +export type PanoramaQueryKey = keyof typeof METABASE_QUERIES.panorama +export type InformeQueryKey = keyof typeof METABASE_QUERIES.informe