cambio heavys
This commit is contained in:
@@ -28,11 +28,58 @@
|
||||
|
||||
<!-- Main Content -->
|
||||
<template v-else>
|
||||
<!-- Metadatos Cards de Ingresos y Clientes -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<MetadatosCard v-if="ingresosMetadata" :metadata="ingresosMetadata" />
|
||||
<MetadatosCard v-if="clientesMetadata" :metadata="clientesMetadata" />
|
||||
</div>
|
||||
<!-- Metadatos Card Colapsable -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<div v-show="!metadatosCollapsed" class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<MetadatosCard v-if="ingresosMetadata" :metadata="ingresosMetadata" />
|
||||
<MetadatosCard v-if="clientesMetadata" :metadata="clientesMetadata" />
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Filtros Card Colapsable -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<UCard v-show="!filtrosCollapsed" class="brand-card border border-transparent">
|
||||
<InformeIngresosFiltrosPanel
|
||||
:clientes="clientes"
|
||||
:selected-cliente-ids="selectedClienteIds"
|
||||
:selected-preset="selectedPreset"
|
||||
:fecha-desde="fechaDesde"
|
||||
:fecha-hasta="fechaHasta"
|
||||
:selected-tipos="selectedTipos"
|
||||
:selected-estados="selectedEstados"
|
||||
:selected-ubicaciones="selectedUbicaciones"
|
||||
:selected-calidades="selectedCalidades"
|
||||
:tipos-options="tiposCafeOptions"
|
||||
:estados-options="estadosOptions"
|
||||
:ubicaciones-options="ubicacionesOptions"
|
||||
:calidades-options="calidadesOptions"
|
||||
:include-anulados="includeAnulados"
|
||||
@update:selected-cliente-ids="selectedClienteIds = $event"
|
||||
@update:selected-preset="selectedPreset = $event"
|
||||
@update:fecha-desde="fechaDesde = $event"
|
||||
@update:fecha-hasta="fechaHasta = $event"
|
||||
@update:selected-tipos="selectedTipos = $event"
|
||||
@update:selected-estados="selectedEstados = $event"
|
||||
@update:selected-ubicaciones="selectedUbicaciones = $event"
|
||||
@update:selected-calidades="selectedCalidades = $event"
|
||||
@update:include-anulados="includeAnulados = $event"
|
||||
/>
|
||||
</UCard>
|
||||
</Transition>
|
||||
|
||||
<!-- Clientes Seleccionados Cards -->
|
||||
<div v-if="clientesSeleccionados.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@@ -46,242 +93,6 @@
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<!-- 🔻 Card de Filtros -->
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold brand-section-title">Filtros y Configuraciones</h2>
|
||||
<p class="text-xs text-[var(--brand-text-muted)] mt-1">
|
||||
Filtros aplicados a ingresos por fecha y cliente
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UCheckbox v-model="includeAnulados" label="Incluir anulados" @update:model-value="onToggleAnulados" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerta roja cuando incluye anulados -->
|
||||
<UAlert
|
||||
v-if="includeAnulados"
|
||||
color="error"
|
||||
variant="solid"
|
||||
icon="i-lucide-alert-triangle"
|
||||
title="Incluir anulados activado"
|
||||
description="Los cálculos incluyen registros anulados. Esto puede afectar los resultados financieros."
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<!-- Fila 1: Selector de Clientes -->
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide mb-3">
|
||||
Clientes
|
||||
</h3>
|
||||
<ClienteSelector
|
||||
:clientes="clientes"
|
||||
:selected-ids="selectedClienteIds"
|
||||
@update:selected-ids="selectedClienteIds = $event"
|
||||
/>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="selectedClienteIds.length > 0"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
@click="selectedClienteIds = []"
|
||||
class="shrink-0"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-lucide-x" />
|
||||
</template>
|
||||
Limpiar
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Fila 2: Selector de Rango de Fechas -->
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide mb-3">
|
||||
Rango de Fechas
|
||||
</h3>
|
||||
<DateRangeSelector
|
||||
:selected-preset="selectedPreset"
|
||||
:fecha-desde="fechaDesde"
|
||||
:fecha-hasta="fechaHasta"
|
||||
@update:selected-preset="selectedPreset = $event"
|
||||
@update:fecha-desde="fechaDesde = $event"
|
||||
@update:fecha-hasta="fechaHasta = $event"
|
||||
/>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="fechaDesde || fechaHasta"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
@click="() => { fechaDesde = null; fechaHasta = null; selectedPreset = '' }"
|
||||
class="shrink-0"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-lucide-x" />
|
||||
</template>
|
||||
Limpiar
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Fila 3: Filtros Avanzados -->
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<div class="flex-1 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<!-- Tipos de Café -->
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide mb-3">
|
||||
Tipos de Café
|
||||
</h3>
|
||||
<UInputMenu
|
||||
v-model="selectedTipos"
|
||||
:items="tiposCafeOptions"
|
||||
value-key="value"
|
||||
multiple
|
||||
placeholder="Todos los tipos"
|
||||
size="sm"
|
||||
icon="i-lucide-coffee"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Estados -->
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide mb-3">
|
||||
Estados
|
||||
</h3>
|
||||
<UInputMenu
|
||||
v-model="selectedEstados"
|
||||
:items="estadosOptions"
|
||||
value-key="value"
|
||||
multiple
|
||||
placeholder="Todos los estados"
|
||||
size="sm"
|
||||
icon="i-lucide-circle-check-big"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Ubicaciones -->
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide mb-3">
|
||||
Ubicaciones
|
||||
</h3>
|
||||
<UInputMenu
|
||||
v-model="selectedUbicaciones"
|
||||
:items="ubicacionesOptions"
|
||||
value-key="value"
|
||||
multiple
|
||||
placeholder="Todas las ubicaciones"
|
||||
size="sm"
|
||||
icon="i-lucide-map-pin"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Calidad -->
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-sm font-semibold text-[var(--brand-text-muted)] uppercase tracking-wide mb-3">
|
||||
Calidad
|
||||
</h3>
|
||||
<UInputMenu
|
||||
v-model="selectedCalidades"
|
||||
:items="calidadesOptions"
|
||||
value-key="value"
|
||||
multiple
|
||||
placeholder="Todas las calidades"
|
||||
size="sm"
|
||||
icon="i-lucide-star"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
v-if="hasAdvancedFilters"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
@click="clearAdvancedFilters"
|
||||
class="shrink-0"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-lucide-x" />
|
||||
</template>
|
||||
Limpiar
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="space-y-3">
|
||||
<!-- Main Stats - Highlighted -->
|
||||
<div class="flex flex-wrap items-center gap-4 p-3 rounded-lg bg-gradient-to-r from-[var(--brand-primary)]/10 to-[var(--brand-primary)]/5 border border-[var(--brand-primary)]/20">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-lucide-calendar-range" class="w-4 h-4 text-[var(--brand-primary)]" />
|
||||
<span class="text-sm font-semibold text-[var(--brand-text)]">
|
||||
{{ rangoLegible }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-[var(--brand-border)]" />
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-lucide-trending-up" class="w-4 h-4 text-cyan-400" />
|
||||
<span class="text-sm font-medium text-[var(--brand-text)]">
|
||||
Ingresos:
|
||||
</span>
|
||||
<span class="text-sm font-bold text-cyan-400">
|
||||
{{ ingresosFiltrados.length }}
|
||||
</span>
|
||||
<span class="text-xs text-[var(--brand-text-muted)]">
|
||||
/ {{ ingresos.length }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-[var(--brand-border)]" />
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-lucide-users" class="w-4 h-4 text-yellow-500" />
|
||||
<span class="text-sm font-medium text-[var(--brand-text)]">
|
||||
Clientes:
|
||||
</span>
|
||||
<span class="text-sm font-bold text-yellow-500">
|
||||
{{ selectedClienteIds.length > 0 ? selectedClienteIds.length : clientesFiltrados.length }}
|
||||
</span>
|
||||
<span class="text-xs text-[var(--brand-text-muted)]">
|
||||
/ {{ clientes.length }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Filters -->
|
||||
<div v-if="hasAdvancedFilters" class="flex flex-wrap gap-2 items-center text-xs">
|
||||
<span class="text-[var(--brand-primary)] font-semibold">Filtros activos:</span>
|
||||
<span v-if="selectedTipos.length > 0" class="inline-flex items-center gap-1 px-2 py-1 rounded bg-[var(--brand-primary)]/20 text-[var(--brand-primary)] border border-[var(--brand-primary)]/30">
|
||||
<UIcon name="i-lucide-coffee" class="w-3 h-3" />
|
||||
{{ selectedTiposLabels }}
|
||||
</span>
|
||||
<span v-if="selectedEstados.length > 0" class="inline-flex items-center gap-1 px-2 py-1 rounded bg-[var(--brand-primary)]/20 text-[var(--brand-primary)] border border-[var(--brand-primary)]/30">
|
||||
<UIcon name="i-lucide-circle-check-big" class="w-3 h-3" />
|
||||
{{ selectedEstadosLabels }}
|
||||
</span>
|
||||
<span v-if="selectedUbicaciones.length > 0" class="inline-flex items-center gap-1 px-2 py-1 rounded bg-[var(--brand-primary)]/20 text-[var(--brand-primary)] border border-[var(--brand-primary)]/30">
|
||||
<UIcon name="i-lucide-map-pin" class="w-3 h-3" />
|
||||
{{ selectedUbicaciones.length }} ubicaciones
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<!-- Totales por Café -->
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
@@ -513,10 +324,13 @@ import type { IngresoRecord } from '~/composables/useIngresosMetrics'
|
||||
|
||||
// Define page metadata
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Analizador Ingresos-Clientes'
|
||||
layout: 'informe',
|
||||
title: 'Informe Ingresos'
|
||||
})
|
||||
|
||||
// Obtener estado colapsado desde el layout
|
||||
const { filtrosCollapsed, metadatosCollapsed, setFiltrosResumen, setDatasourceCounts, setFilteredResults, setActiveFilters } = useInformeLayout()
|
||||
|
||||
// View modes with explicit hierarchy
|
||||
type ViewMode = 'ingresos-only' | 'clientes-only' | 'ingresos-clientes' | 'clientes-ingresos'
|
||||
const selectedView = ref<ViewMode>('ingresos-only')
|
||||
@@ -984,6 +798,177 @@ onMounted(async () => {
|
||||
|
||||
// Listener para escape key en fullscreen
|
||||
window.addEventListener('keydown', handleEscape)
|
||||
|
||||
// Enviar counts de datasources al layout
|
||||
setDatasourceCounts({
|
||||
ingresos: ingresos.value.length,
|
||||
clientes: clientes.value.length
|
||||
})
|
||||
|
||||
// Watch para actualizar counts de datasources cuando cambien
|
||||
watch([ingresos, clientes], () => {
|
||||
setDatasourceCounts({
|
||||
ingresos: ingresos.value.length,
|
||||
clientes: clientes.value.length
|
||||
})
|
||||
})
|
||||
|
||||
// Watch para actualizar resultados filtrados en el navbar
|
||||
watch([ingresosFiltrados, clientesFiltrados], () => {
|
||||
setFilteredResults({
|
||||
ingresos: ingresosFiltrados.value.length,
|
||||
clientes: clientesFiltrados.value.length
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
// Actualizar resumen de filtros cuando cambien
|
||||
watch([selectedClienteIds, fechaDesde, fechaHasta, selectedTipos, selectedEstados, selectedUbicaciones, selectedCalidades, includeAnulados], () => {
|
||||
const filtrosActivos: string[] = []
|
||||
|
||||
if (selectedClienteIds.value.length > 0) {
|
||||
filtrosActivos.push(`${selectedClienteIds.value.length} cliente${selectedClienteIds.value.length > 1 ? 's' : ''}`)
|
||||
}
|
||||
if (fechaDesde.value || fechaHasta.value) {
|
||||
filtrosActivos.push('rango de fechas')
|
||||
}
|
||||
if (selectedTipos.value.length > 0) {
|
||||
filtrosActivos.push(`${selectedTipos.value.length} tipo${selectedTipos.value.length > 1 ? 's' : ''}`)
|
||||
}
|
||||
if (selectedEstados.value.length > 0) {
|
||||
filtrosActivos.push(`${selectedEstados.value.length} estado${selectedEstados.value.length > 1 ? 's' : ''}`)
|
||||
}
|
||||
if (selectedUbicaciones.value.length > 0) {
|
||||
filtrosActivos.push(`${selectedUbicaciones.value.length} ubicación${selectedUbicaciones.value.length > 1 ? 'es' : ''}`)
|
||||
}
|
||||
if (selectedCalidades.value.length > 0) {
|
||||
filtrosActivos.push(`${selectedCalidades.value.length} calidad${selectedCalidades.value.length > 1 ? 'es' : ''}`)
|
||||
}
|
||||
if (includeAnulados.value) {
|
||||
filtrosActivos.push('incluye anulados')
|
||||
}
|
||||
|
||||
if (filtrosActivos.length > 0) {
|
||||
setFiltrosResumen(
|
||||
filtrosActivos.length,
|
||||
filtrosActivos.join(', '),
|
||||
ingresosFiltrados.value.length
|
||||
)
|
||||
} else {
|
||||
setFiltrosResumen(0, '', ingresosFiltrados.value.length)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Actualizar filtros activos detallados para el navbar
|
||||
watch([selectedClienteIds, fechaDesde, fechaHasta, selectedTipos, selectedEstados, selectedUbicaciones, selectedCalidades, includeAnulados, clientes], () => {
|
||||
const filters: any[] = []
|
||||
|
||||
// Clientes seleccionados (mostrar cada cliente)
|
||||
if (selectedClienteIds.value.length > 0) {
|
||||
selectedClienteIds.value.forEach(id => {
|
||||
const cliente = clientes.value?.find(c => c.id === id)
|
||||
if (cliente) {
|
||||
filters.push({
|
||||
type: 'cliente',
|
||||
label: `👤 ${cliente.name}`,
|
||||
value: id,
|
||||
onRemove: () => {
|
||||
selectedClienteIds.value = selectedClienteIds.value.filter(cid => cid !== id)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Rango de fechas
|
||||
if (fechaDesde.value || fechaHasta.value) {
|
||||
filters.push({
|
||||
type: 'fechas',
|
||||
label: `📅 ${fechaDesde.value || '—'} → ${fechaHasta.value || '—'}`,
|
||||
value: { desde: fechaDesde.value, hasta: fechaHasta.value },
|
||||
onRemove: () => {
|
||||
fechaDesde.value = null
|
||||
fechaHasta.value = null
|
||||
selectedPreset.value = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tipos de café
|
||||
if (selectedTipos.value.length > 0) {
|
||||
selectedTipos.value.forEach(tipo => {
|
||||
const tipoObj = tiposCafeOptions.find(t => t.value === tipo)
|
||||
if (tipoObj) {
|
||||
filters.push({
|
||||
type: 'tipo',
|
||||
label: `☕ ${tipoObj.label}`,
|
||||
value: tipo,
|
||||
onRemove: () => {
|
||||
selectedTipos.value = selectedTipos.value.filter(t => t !== tipo)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Estados
|
||||
if (selectedEstados.value.length > 0) {
|
||||
selectedEstados.value.forEach(estado => {
|
||||
const estadoObj = estadosOptions.find(e => e.value === estado)
|
||||
if (estadoObj) {
|
||||
filters.push({
|
||||
type: 'estado',
|
||||
label: `✓ ${estadoObj.label}`,
|
||||
value: estado,
|
||||
onRemove: () => {
|
||||
selectedEstados.value = selectedEstados.value.filter(e => e !== estado)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Ubicaciones
|
||||
if (selectedUbicaciones.value.length > 0) {
|
||||
selectedUbicaciones.value.forEach(ubicacion => {
|
||||
filters.push({
|
||||
type: 'ubicacion',
|
||||
label: `📍 ${ubicacion}`,
|
||||
value: ubicacion,
|
||||
onRemove: () => {
|
||||
selectedUbicaciones.value = selectedUbicaciones.value.filter(u => u !== ubicacion)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Calidades
|
||||
if (selectedCalidades.value.length > 0) {
|
||||
selectedCalidades.value.forEach(calidad => {
|
||||
filters.push({
|
||||
type: 'calidad',
|
||||
label: `⭐ ${calidad}`,
|
||||
value: calidad,
|
||||
onRemove: () => {
|
||||
selectedCalidades.value = selectedCalidades.value.filter(c => c !== calidad)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Incluir anulados
|
||||
if (includeAnulados.value) {
|
||||
filters.push({
|
||||
type: 'anulados',
|
||||
label: '⚠️ Con anulados',
|
||||
value: true,
|
||||
onRemove: () => {
|
||||
includeAnulados.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setActiveFilters(filters)
|
||||
}, { immediate: true })
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ import type { RechazoRecord } from '~/composables/useRechazosMetrics'
|
||||
|
||||
// Define page metadata
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
layout: 'informe',
|
||||
title: 'Panorama Facturador'
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user