Agregar sección Contactos con UTabs y conexión a Metabase
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m37s

- Implementar UTabs (Contactos, Aplicaciones, Perfil) en app.vue
- Crear componentes ContactsList, ContactsFilters, ContactItem
- Agregar server routes para obtener contactos via Metabase API
- Sistema de aliases por usuario guardados en archivos JSON
- Filtros: nombre (fuzzy search), ID, teléfono, empleado
- Click en contacto abre WhatsApp
- Estilo glassmorphism consistente con la app
This commit is contained in:
2025-12-05 11:41:26 -06:00
parent 00596bd6df
commit 59f25adabe
13 changed files with 1512 additions and 17 deletions

View File

@@ -0,0 +1,111 @@
/**
* API endpoint para obtener contactos desde Metabase
* Consulta la tabla Clientes del proyecto facturador en Supabase via Metabase API
*/
interface Contact {
id: number
name: string
cedula: number | null
ubicacion: string | null
grupo_estudio: string | null
empleado: boolean
avatar_url: string | null
telefono: string | null
idciat: string | null
}
interface MetabaseResponse {
data: {
cols: Array<{ name: string }>
rows: Array<Array<unknown>>
}
}
export default defineEventHandler(async (event): Promise<Contact[]> => {
const config = useRuntimeConfig()
const headers = getRequestHeaders(event)
const query = getQuery(event)
// Verificar autenticación
const uid = headers['x-authentik-uid']
if (!uid) {
throw createError({
statusCode: 401,
message: 'Usuario no autenticado'
})
}
// Obtener configuración de Metabase
const metabaseUrl = config.metabaseApiUrl as string
const metabaseApiKey = config.metabaseApiKey as string
const databaseId = config.metabaseDatabaseId as number
const tableId = config.metabaseTableId as number
if (!metabaseApiKey) {
throw createError({
statusCode: 500,
message: 'API Key de Metabase no configurada'
})
}
// Construir filtros para la query
const filters: unknown[] = []
// Filtro de empleados (por defecto true)
const empleadoFilter = query.empleado !== 'false'
if (empleadoFilter) {
filters.push(['=', ['field', 'empleado', { 'base-type': 'type/Boolean' }], true])
}
// Filtro por ID exacto
if (query.id) {
const idNum = parseInt(query.id as string)
if (!isNaN(idNum)) {
filters.push(['=', ['field', 'id', { 'base-type': 'type/BigInteger' }], idNum])
}
}
// Construir la query para Metabase
const metabaseQuery = {
database: databaseId,
type: 'query',
query: {
'source-table': tableId,
'order-by': [['asc', ['field', 'id', { 'base-type': 'type/BigInteger' }]]],
filter: filters.length > 0
? (filters.length === 1 ? filters[0] : ['and', ...filters])
: undefined
}
}
try {
const response = await $fetch<MetabaseResponse>(`${metabaseUrl}/api/dataset`, {
method: 'POST',
headers: {
'X-API-Key': metabaseApiKey,
'Content-Type': 'application/json'
},
body: metabaseQuery
})
// Mapear columnas a objetos
const cols = response.data.cols.map((col) => col.name)
const contacts: Contact[] = response.data.rows.map((row) => {
const contact: Record<string, unknown> = {}
cols.forEach((colName, index) => {
contact[colName] = row[index]
})
return contact as unknown as Contact
})
return contacts
} catch (error: unknown) {
console.error('Error al obtener contactos de Metabase:', error)
const err = error as { statusCode?: number; message?: string }
throw createError({
statusCode: err.statusCode || 500,
message: err.message || 'Error al obtener los contactos'
})
}
})