@@ -43,7 +43,13 @@
+
+
+
@@ -94,12 +100,18 @@ const empleadoFilter = computed({
set: (val) => emit('update:modelValue', { ...props.modelValue, empleado: val })
})
+const presenteFilter = computed({
+ get: () => props.modelValue.presente,
+ set: (val) => emit('update:modelValue', { ...props.modelValue, presente: val })
+})
+
const hasActiveFilters = computed(() => {
return (
props.modelValue.search ||
props.modelValue.id ||
props.modelValue.telefono ||
- !props.modelValue.empleado
+ !props.modelValue.empleado ||
+ props.modelValue.presente
)
})
diff --git a/nuxt4/app/components/contacts/List.vue b/nuxt4/app/components/contacts/List.vue
index 94e50ab..501b9b3 100644
--- a/nuxt4/app/components/contacts/List.vue
+++ b/nuxt4/app/components/contacts/List.vue
@@ -71,6 +71,7 @@ const {
error,
fetchContacts,
fetchAliases,
+ fetchPresentes,
updateAlias,
getWhatsAppUrl,
clearFilters
@@ -78,7 +79,7 @@ const {
// Cargar datos al montar
onMounted(async () => {
- await Promise.all([fetchContacts(), fetchAliases()])
+ await Promise.all([fetchContacts(), fetchAliases(), fetchPresentes()])
})
// Manejar actualización de alias
diff --git a/nuxt4/app/composables/useContacts.ts b/nuxt4/app/composables/useContacts.ts
index 09708c2..f84b8e0 100644
--- a/nuxt4/app/composables/useContacts.ts
+++ b/nuxt4/app/composables/useContacts.ts
@@ -19,6 +19,7 @@ export interface ContactFilters {
id: string
telefono: string
empleado: boolean
+ presente: boolean
}
export const useContacts = () => {
@@ -27,6 +28,7 @@ export const useContacts = () => {
// Estado
const contacts = ref
([])
const aliases = ref>({})
+ const presentIds = ref>(new Set())
const isLoading = ref(false)
const error = ref(null)
@@ -37,7 +39,8 @@ export const useContacts = () => {
search: '',
id: '',
telefono: '',
- empleado: true
+ empleado: true,
+ presente: false
})
})
@@ -87,6 +90,19 @@ export const useContacts = () => {
}
}
+ /**
+ * Cargar IDs de empleados presentes
+ */
+ const fetchPresentes = async () => {
+ try {
+ const data = await $fetch('/api/contacts/presentes')
+ presentIds.value = new Set(data)
+ } catch (err: any) {
+ console.error('Error al cargar presentes:', err)
+ // No es crítico, continuar sin filtro de presentes
+ }
+ }
+
/**
* Actualizar alias de un contacto
*/
@@ -119,6 +135,11 @@ export const useContacts = () => {
// Primero filtrar solo contactos con teléfono
let result = contacts.value.filter(c => c.telefono && c.telefono.trim() !== '')
+ // Filtro por presentes (solo si está activo)
+ if (filters.value.presente) {
+ result = result.filter(c => presentIds.value.has(c.id))
+ }
+
// Filtro por teléfono (parcial)
if (filters.value.telefono) {
const telFilter = filters.value.telefono.replace(/\D/g, '')
@@ -177,7 +198,8 @@ export const useContacts = () => {
search: '',
id: '',
telefono: '',
- empleado: true
+ empleado: true,
+ presente: false
}
}
@@ -189,15 +211,27 @@ export const useContacts = () => {
}
)
+ // Watch para recargar presentes cuando se activa el filtro
+ watch(
+ () => filters.value.presente,
+ (newVal) => {
+ if (newVal && presentIds.value.size === 0) {
+ fetchPresentes()
+ }
+ }
+ )
+
return {
contacts,
aliases,
+ presentIds,
filters,
filteredContacts,
isLoading,
error,
fetchContacts,
fetchAliases,
+ fetchPresentes,
updateAlias,
getDisplayName,
hasAlias,
diff --git a/nuxt4/server/api/contacts/presentes.get.ts b/nuxt4/server/api/contacts/presentes.get.ts
new file mode 100644
index 0000000..bcf785d
--- /dev/null
+++ b/nuxt4/server/api/contacts/presentes.get.ts
@@ -0,0 +1,86 @@
+/**
+ * API endpoint para obtener IDs de empleados presentes
+ * Consulta la tabla Asistencias del proyecto facturador en Supabase via Metabase API
+ * Un empleado está "presente" si su última asistencia tiene entrada pero NO tiene salida
+ */
+
+interface MetabaseResponse {
+ data: {
+ cols: Array<{ name: string }>
+ rows: Array>
+ }
+}
+
+export default defineEventHandler(async (event): Promise => {
+ const config = useRuntimeConfig()
+ const headers = getRequestHeaders(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
+
+ if (!metabaseApiKey) {
+ throw createError({
+ statusCode: 500,
+ message: 'API Key de Metabase no configurada'
+ })
+ }
+
+ // Query nativa para obtener empleados presentes
+ // Busca la última asistencia de cada empleado donde salida es NULL
+ const nativeQuery = `
+ WITH ultima_asistencia AS (
+ SELECT
+ empleado_id,
+ entrada,
+ salida,
+ ROW_NUMBER() OVER (PARTITION BY empleado_id ORDER BY entrada DESC) as rn
+ FROM asistencias
+ WHERE entrada IS NOT NULL
+ )
+ SELECT empleado_id
+ FROM ultima_asistencia
+ WHERE rn = 1 AND salida IS NULL
+ `
+
+ const metabaseQuery = {
+ database: databaseId,
+ type: 'native',
+ native: {
+ query: nativeQuery
+ }
+ }
+
+ try {
+ const response = await $fetch(`${metabaseUrl}/api/dataset`, {
+ method: 'POST',
+ headers: {
+ 'X-API-Key': metabaseApiKey,
+ 'Content-Type': 'application/json'
+ },
+ body: metabaseQuery
+ })
+
+ // Extraer los IDs de empleados presentes
+ const presentIds: number[] = response.data.rows.map(row => row[0] as number)
+
+ return presentIds
+ } catch (error: unknown) {
+ console.error('Error al obtener empleados presentes de Metabase:', error)
+ const err = error as { statusCode?: number; message?: string }
+ throw createError({
+ statusCode: err.statusCode || 500,
+ message: err.message || 'Error al obtener empleados presentes'
+ })
+ }
+})