diff --git a/nuxt4-app/app/components/ClienteMultiSelector.vue b/nuxt4-app/app/components/ClienteMultiSelector.vue
new file mode 100644
index 0000000..6db7f3e
--- /dev/null
+++ b/nuxt4-app/app/components/ClienteMultiSelector.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedIds.length }} cliente{{ selectedIds.length !== 1 ? 's' : '' }} seleccionado{{ selectedIds.length !== 1 ? 's' : '' }}
+
+
+ Limpiar todo
+
+
+
+
+
+
+ Cargando clientes...
+
+
+
+ {{ searchQuery ? 'No se encontraron clientes' : 'No hay clientes disponibles' }}
+
+
+
+
+
+
+ {{ cliente.name }}
+
+
+ {{ formatCedula(cliente.cedula) }}
+ • {{ cliente.ubicacion }}
+
+
+
+
+
+
+
+
+ Filtrar por ubicación
+
+
+
+ {{ ubicacion }}
+
+
+
+
+
+
+
diff --git a/nuxt4-app/app/pages/informe-ingresos.vue b/nuxt4-app/app/pages/informe-ingresos.vue
index 5a0e22b..ff5e95b 100644
--- a/nuxt4-app/app/pages/informe-ingresos.vue
+++ b/nuxt4-app/app/pages/informe-ingresos.vue
@@ -61,7 +61,30 @@
Filtros Avanzados
-
+
+
+
+
+
+
+
+
+
+
@@ -82,6 +105,20 @@
+
+
+
@@ -190,7 +227,30 @@
Filtros Avanzados
-
+
+
+
+
+
+
+
+
+
+
@@ -212,10 +272,19 @@
-
-
- Los filtros de clientes, ubicaciones y calidades se agregarán próximamente
-
+
+
@@ -354,6 +423,11 @@ const selectedPreset = ref('cosecha-25-26')
const fechaDesde = ref(null)
const fechaHasta = ref(null)
+// Filtros avanzados - clientes y ubicaciones
+const selectedClienteIds = ref([])
+const selectedUbicaciones = ref([])
+const selectedCalidades = ref([])
+
// Filtros avanzados - usando checkboxes separados
const filterTipos = ref({
uva: false,
@@ -367,6 +441,14 @@ const filterEstados = ref({
pendiente: false
})
+// Opciones de filtros disponibles (desde Metabase)
+const opcionesFiltros = ref({
+ ubicaciones: [] as string[],
+ calidades: [] as string[],
+ tipos: [] as string[],
+ estados: [] as string[]
+})
+
// Convertir checkboxes a arrays para el API
const tiposArray = computed(() => {
const tipos: string[] = []
@@ -389,6 +471,9 @@ const appliedFilters = ref<{
fechaDesde: string | null
fechaHasta: string | null
includeAnulados: boolean
+ clienteIds: number[]
+ ubicaciones: string[]
+ calidades: string[]
tipos: string[]
estados: string[]
} | null>(null)
@@ -411,6 +496,9 @@ const hasPendingChanges = computed(() => {
fechaDesde.value !== appliedFilters.value.fechaDesde ||
fechaHasta.value !== appliedFilters.value.fechaHasta ||
includeAnulados.value !== appliedFilters.value.includeAnulados ||
+ JSON.stringify(selectedClienteIds.value) !== JSON.stringify(appliedFilters.value.clienteIds) ||
+ JSON.stringify(selectedUbicaciones.value) !== JSON.stringify(appliedFilters.value.ubicaciones) ||
+ JSON.stringify(selectedCalidades.value) !== JSON.stringify(appliedFilters.value.calidades) ||
JSON.stringify(tiposArray.value) !== JSON.stringify(appliedFilters.value.tipos) ||
JSON.stringify(estadosArray.value) !== JSON.stringify(appliedFilters.value.estados)
)
@@ -432,11 +520,11 @@ async function loadData() {
fecha_desde: fechaDesde.value,
fecha_hasta: fechaHasta.value,
incluir_anulados: includeAnulados.value,
- cliente_ids: [], // TODO: implementar selector de clientes
+ cliente_ids: selectedClienteIds.value,
tipos: tiposArray.value,
estados: estadosArray.value,
- ubicaciones: [], // TODO: implementar selector de ubicaciones
- calidades: [], // TODO: implementar selector de calidades
+ ubicaciones: selectedUbicaciones.value,
+ calidades: selectedCalidades.value,
granularidad: 'dia' // Default granularity
}
@@ -460,6 +548,9 @@ async function loadData() {
fechaDesde: fechaDesde.value,
fechaHasta: fechaHasta.value,
includeAnulados: includeAnulados.value,
+ clienteIds: [...selectedClienteIds.value],
+ ubicaciones: [...selectedUbicaciones.value],
+ calidades: [...selectedCalidades.value],
tipos: [...tiposArray.value],
estados: [...estadosArray.value]
}
@@ -505,10 +596,52 @@ function onUpdateFechaHasta(value: string | null) {
fechaHasta.value = value
}
+// 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)
+ }
+ } 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
+ }
+}
+
// Inicializar preset por defecto sin cargar datos
-onMounted(() => {
+onMounted(async () => {
// Default preset: cosecha 25-26
selectedPreset.value = 'cosecha-25-26'
+
+ // Cargar opciones de filtros disponibles
+ await loadOpcionesFiltros()
+
// NO cargar datos automáticamente - el usuario debe hacer clic en "Actualizar"
})
diff --git a/nuxt4-app/server/api/clientes/index.get.ts b/nuxt4-app/server/api/clientes/index.get.ts
new file mode 100644
index 0000000..e063065
--- /dev/null
+++ b/nuxt4-app/server/api/clientes/index.get.ts
@@ -0,0 +1,33 @@
+/**
+ * Get all clients from Supabase facturador database
+ * 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
+ `
+
+ const result = await $fetch('/api/postgres/query', {
+ method: 'POST',
+ body: { query }
+ })
+
+ return result
+ } catch (error: any) {
+ console.error('[API] Failed to fetch clientes:', error)
+ throw createError({
+ statusCode: error.statusCode || 500,
+ statusMessage: error.statusMessage || 'Failed to fetch clientes'
+ })
+ }
+})
diff --git a/nuxt4-app/server/api/postgres/query.post.ts b/nuxt4-app/server/api/postgres/query.post.ts
new file mode 100644
index 0000000..2a1ca6d
--- /dev/null
+++ b/nuxt4-app/server/api/postgres/query.post.ts
@@ -0,0 +1,34 @@
+/**
+ * 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'
+ })
+ }
+})