diff --git a/nuxt4-app/app/components/empleados/TotalesEmpleados.vue b/nuxt4-app/app/components/empleados/TotalesEmpleados.vue new file mode 100644 index 0000000..094e529 --- /dev/null +++ b/nuxt4-app/app/components/empleados/TotalesEmpleados.vue @@ -0,0 +1,203 @@ + + + diff --git a/nuxt4-app/app/pages/informe-empleados.vue b/nuxt4-app/app/pages/informe-empleados.vue new file mode 100644 index 0000000..4dbb7da --- /dev/null +++ b/nuxt4-app/app/pages/informe-empleados.vue @@ -0,0 +1,447 @@ + + + diff --git a/nuxt4-app/server/api/metabase/informe-empleados.post.ts b/nuxt4-app/server/api/metabase/informe-empleados.post.ts new file mode 100644 index 0000000..2e6d5a6 --- /dev/null +++ b/nuxt4-app/server/api/metabase/informe-empleados.post.ts @@ -0,0 +1,183 @@ +import { METABASE_QUERIES } from '../../config/metabase-queries' + +/** + * Execute all informe empleados queries in parallel + * Returns data for the Informe de Empleados page + */ +export default defineEventHandler(async (event) => { + const body = await readBody(event) + + const { + fecha_desde = null, + fecha_hasta = null, + empleado_ids = [], + titulos_tareas = [], + titulos_planillas = [] + } = body + + try { + // First, get all cards to find our informe empleados queries + const allCards = await getMetabaseCards('all') + + // Find our informe empleados queries by name using centralized config + const queryNames = METABASE_QUERIES.informe_empleados + + 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 Empleados] Query not found: ${name}`) + } else { + cards[key] = card + } + } + + // Build parameters array for Metabase queries + const buildParameters = () => { + const params = [ + { + type: 'text', + target: ['variable', ['template-tag', 'fecha_desde']], + value: fecha_desde || '' + }, + { + type: 'text', + target: ['variable', ['template-tag', 'fecha_hasta']], + value: fecha_hasta || '' + } + ] + + // Solo agregar filtros opcionales si tienen valores (no vacíos) + if (empleado_ids && Array.isArray(empleado_ids) && empleado_ids.length > 0) { + params.push({ + type: 'number', + target: ['variable', ['template-tag', 'empleado_ids']], + value: empleado_ids + }) + } + + if (titulos_tareas && Array.isArray(titulos_tareas) && titulos_tareas.length > 0) { + params.push({ + type: 'text', + target: ['variable', ['template-tag', 'titulos_tareas']], + value: titulos_tareas + }) + } + + if (titulos_planillas && Array.isArray(titulos_planillas) && titulos_planillas.length > 0) { + params.push({ + type: 'text', + target: ['variable', ['template-tag', 'titulos_planillas']], + value: titulos_planillas + }) + } + + return params + } + + const standardParams = buildParameters() + const emptyParams: any[] = [] // Para opciones_filtros que no requiere parámetros + + // 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 Empleados] No card ID for ${name}`) + return defaultValue + } + + try { + console.log(`[Informe Empleados] Executing query: ${name} (ID: ${cardId})`) + const result = await executeCardQuery(cardId, parameters) + console.log(`[Informe Empleados] Query ${name} returned ${result.data?.rows?.length || 0} rows`) + return result + } catch (error: any) { + console.error(`[Informe Empleados] Error executing ${name}:`, error.message) + return defaultValue + } + } + + const [ + contadores, + listaEmpleados, + detalleTareas, + detalleAsistencias, + opcionesFiltros + ] = await Promise.all([ + executeWithErrorHandling('contadores', cards.contadores?.id, standardParams, { data: { rows: [[]], cols: [] } }), + executeWithErrorHandling('lista_empleados', cards.lista_empleados?.id, standardParams, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('detalle_tareas', cards.detalle_tareas?.id, standardParams, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('detalle_asistencias', cards.detalle_asistencias?.id, standardParams, { data: { rows: [], cols: [] } }), + executeWithErrorHandling('opciones_filtros', cards.opciones_filtros?.id, emptyParams, { 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 + }) + } + + // Transform opciones_filtros to a more usable format + const transformOpcionesFiltros = (result: any) => { + if (!result.data?.rows || !result.data?.cols) { + return { + titulos_tareas: [], + titulos_planillas: [] + } + } + + const rows = transformMultipleRows(result) + const opciones: any = { + titulos_tareas: [], + titulos_planillas: [] + } + + rows.forEach((row: any) => { + if (row.tipo_opcion === 'titulos_tareas') { + opciones.titulos_tareas.push(row.valor) + } else if (row.tipo_opcion === 'titulos_planillas') { + opciones.titulos_planillas.push(row.valor) + } + }) + + return opciones + } + + // Return all data in a structured format + return { + contadores: transformSingleRow(contadores), + listaEmpleados: transformMultipleRows(listaEmpleados), + detalleTareas: transformMultipleRows(detalleTareas), + detalleAsistencias: transformMultipleRows(detalleAsistencias), + opcionesFiltros: transformOpcionesFiltros(opcionesFiltros) + } + } catch (error: any) { + console.error('[API] Failed to execute informe empleados queries:', error) + throw createError({ + statusCode: error.statusCode || 500, + statusMessage: error.statusMessage || 'Failed to execute informe empleados queries' + }) + } +}) diff --git a/nuxt4-app/server/config/metabase-queries.ts b/nuxt4-app/server/config/metabase-queries.ts index 3717389..33b77bb 100644 --- a/nuxt4-app/server/config/metabase-queries.ts +++ b/nuxt4-app/server/config/metabase-queries.ts @@ -57,6 +57,17 @@ export const METABASE_QUERIES = { opciones_filtros: 'Informe Comercios - Opciones de Filtros', contadores: 'Informe Comercios - Contadores de Filtros', detalle_ingresos: 'Informe Comercios - Detalle de Ingresos por Comercio' + }, + + /** + * Queries para Informe de Empleados + */ + informe_empleados: { + contadores: 'Informe Empleados - Contadores', + lista_empleados: 'Informe Empleados - Lista con Totales', + detalle_tareas: 'Informe Empleados - Detalle Tareas', + detalle_asistencias: 'Informe Empleados - Detalle Asistencias', + opciones_filtros: 'Informe Empleados - Opciones Filtros' } } as const @@ -68,3 +79,4 @@ export type PanoramaQueryKey = keyof typeof METABASE_QUERIES.panorama export type InformeQueryKey = keyof typeof METABASE_QUERIES.informe export type ComparativaQueryKey = keyof typeof METABASE_QUERIES.comparativa export type InformeComerciosQueryKey = keyof typeof METABASE_QUERIES.informe_comercios +export type InformeEmpleadosQueryKey = keyof typeof METABASE_QUERIES.informe_empleados