diff --git a/nuxt4-app/app/components/ingresos/InventariosDeposito.vue b/nuxt4-app/app/components/ingresos/InventariosDeposito.vue
index 3c3a259..55d3115 100644
--- a/nuxt4-app/app/components/ingresos/InventariosDeposito.vue
+++ b/nuxt4-app/app/components/ingresos/InventariosDeposito.vue
@@ -6,21 +6,21 @@
()
const formatCurrency = (value: number) => {
- return new Intl.NumberFormat('es-GT', {
+ return new Intl.NumberFormat('es-HN', {
style: 'currency',
- currency: 'GTQ'
- }).format(value)
+ currency: 'HNL',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ }).format(value).replace('HNL', 'L')
}
\ No newline at end of file
diff --git a/nuxt4-app/app/components/ingresos/InversionTotal.vue b/nuxt4-app/app/components/ingresos/InversionTotal.vue
index e61742f..da08cb6 100644
--- a/nuxt4-app/app/components/ingresos/InversionTotal.vue
+++ b/nuxt4-app/app/components/ingresos/InversionTotal.vue
@@ -38,9 +38,11 @@ defineProps<{
}>()
const formatCurrency = (value: number) => {
- return new Intl.NumberFormat('es-GT', {
+ return new Intl.NumberFormat('es-HN', {
style: 'currency',
- currency: 'GTQ'
- }).format(value)
+ currency: 'HNL',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ }).format(value).replace('HNL', 'L')
}
\ No newline at end of file
diff --git a/nuxt4-app/app/components/ingresos/SecosVendidos.vue b/nuxt4-app/app/components/ingresos/SecosVendidos.vue
index 0c3272c..2914515 100644
--- a/nuxt4-app/app/components/ingresos/SecosVendidos.vue
+++ b/nuxt4-app/app/components/ingresos/SecosVendidos.vue
@@ -36,9 +36,11 @@ defineProps<{
}>()
const formatCurrency = (value: number) => {
- return new Intl.NumberFormat('es-GT', {
+ return new Intl.NumberFormat('es-HN', {
style: 'currency',
- currency: 'GTQ'
- }).format(value)
+ currency: 'HNL',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ }).format(value).replace('HNL', 'L')
}
\ No newline at end of file
diff --git a/nuxt4-app/app/components/ingresos/TotalesIngresoCompra.vue b/nuxt4-app/app/components/ingresos/TotalesIngresoCompra.vue
index c452f09..6428091 100644
--- a/nuxt4-app/app/components/ingresos/TotalesIngresoCompra.vue
+++ b/nuxt4-app/app/components/ingresos/TotalesIngresoCompra.vue
@@ -6,31 +6,31 @@
diff --git a/nuxt4-app/app/components/ingresos/TotalesVerde.vue b/nuxt4-app/app/components/ingresos/TotalesVerde.vue
index f6467c0..088bad9 100644
--- a/nuxt4-app/app/components/ingresos/TotalesVerde.vue
+++ b/nuxt4-app/app/components/ingresos/TotalesVerde.vue
@@ -13,7 +13,7 @@
()
const formatCurrency = (value: number) => {
- return new Intl.NumberFormat('es-GT', {
+ return new Intl.NumberFormat('es-HN', {
style: 'currency',
- currency: 'GTQ'
- }).format(value)
+ currency: 'HNL',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ }).format(value).replace('HNL', 'L')
}
\ No newline at end of file
diff --git a/nuxt4-app/app/components/rechazos/RechazoCard.vue b/nuxt4-app/app/components/rechazos/RechazoCard.vue
index a50eb5b..042938a 100644
--- a/nuxt4-app/app/components/rechazos/RechazoCard.vue
+++ b/nuxt4-app/app/components/rechazos/RechazoCard.vue
@@ -45,9 +45,11 @@ const borderColor = computed(() => {
})
const formatCurrency = (value: number) => {
- return new Intl.NumberFormat('es-GT', {
+ return new Intl.NumberFormat('es-HN', {
style: 'currency',
- currency: 'GTQ'
- }).format(value)
+ currency: 'HNL',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ }).format(value).replace('HNL', 'L')
}
\ No newline at end of file
diff --git a/nuxt4-app/app/components/rechazos/RechazosSubproductos.vue b/nuxt4-app/app/components/rechazos/RechazosSubproductos.vue
index a294309..ee8c84b 100644
--- a/nuxt4-app/app/components/rechazos/RechazosSubproductos.vue
+++ b/nuxt4-app/app/components/rechazos/RechazosSubproductos.vue
@@ -61,9 +61,11 @@ const props = defineProps<{
const totalRechazos = computed(() => props.metrics.totalRechazos)
const formatCurrency = (value: number) => {
- return new Intl.NumberFormat('es-GT', {
+ return new Intl.NumberFormat('es-HN', {
style: 'currency',
- currency: 'GTQ'
- }).format(value)
+ currency: 'HNL',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ }).format(value).replace('HNL', 'L')
}
\ No newline at end of file
diff --git a/nuxt4-app/app/composables/useRechazosMetrics.ts b/nuxt4-app/app/composables/useRechazosMetrics.ts
index 9252f13..286a488 100644
--- a/nuxt4-app/app/composables/useRechazosMetrics.ts
+++ b/nuxt4-app/app/composables/useRechazosMetrics.ts
@@ -30,8 +30,10 @@ export function useRechazosMetrics(rechazos: ComputedRef) {
const registros = rechazos.value.filter(r => r.tipo === tipo)
const totalCantidad = registros.reduce((sum, r) => sum + (r.cantidad || 0), 0)
- const totalCobrado = registros.reduce((sum, r) => sum + (r.total_cobrado || 0), 0)
- const precioPromedio = totalCantidad > 0 ? totalCobrado / totalCantidad : 0
+ // const totalCobrado = registros.reduce((sum, r) => sum + (r.total_cobrado || 0), 0) DESACTIVADO HASTA NORMALIZAR LOS DATOS DE LA TABLA ORIGINAL, MENCIONAR SI TE TOPAS CON ESTO UN MES DESPUES ESTAMOS EN 1 OCTUBRE 2025
+ // const precioPromedio = totalCantidad > 0 ? totalCobrado / totalCantidad : 0 DESACTIVADO HASTA NORMALIZAR LOS DATOS DE LA TABLA ORIGINAL, MENCIONAR SI TE TOPAS CON ESTO UN MES DESPUES ESTAMOS EN 1 OCTUBRE 2025
+ const precioPromedio = 10
+ const totalCobrado = 100
return {
totalCantidad,
diff --git a/nuxt4-app/app/pages/panorama.vue b/nuxt4-app/app/pages/panorama.vue
index dcfa8d2..f91b18d 100644
--- a/nuxt4-app/app/pages/panorama.vue
+++ b/nuxt4-app/app/pages/panorama.vue
@@ -1,3 +1,4 @@
+
@@ -33,6 +34,74 @@
+
+
+
+
+
+
Filtros
+
+ Aplicados a created_at de ingresos y rechazos
+
+
+
+
+ Incluir anulados
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Limpiar
+
+
+
+
+
+
+ Rango activo: {{ rangoLegible }} · Registros considerados: Ingresos {{ ingresosFiltrados.length }}/{{ ingresos.length }} · Rechazos {{ rechazosFiltrados.length }}/{{ rechazos.length }}
+
+
+
+
@@ -115,13 +184,166 @@ definePageMeta({
const ingresosStore = useTableDataStore('ingresos')
const rechazosStore = useTableDataStore('rechazos')
-// Reactive data from stores
+// Reactive data from stores (sin filtrar)
const ingresos = computed(() => ingresosStore.allRecords as IngresoRecord[])
const rechazos = computed(() => rechazosStore.allRecords as RechazoRecord[])
-// Calculate metrics using composables
-const ingresosMetrics = useIngresosMetrics(ingresos)
-const rechazosMetrics = useRechazosMetrics(rechazos)
+// -------------------------------
+// Filtros
+// -------------------------------
+const includeAnulados = ref(false)
+
+type PresetValue =
+ | '' | 'custom' | 'hoy' | 'semana' | 'mes' | 'ytd'
+ | 'cosecha-20-21' | 'cosecha-21-22' | 'cosecha-22-23'
+ | 'cosecha-23-24' | 'cosecha-24-25' | 'cosecha-25-26';
+
+const selectedPreset = ref('cosecha-25-26')
+
+const currentPresetLabel = computed(() => {
+ switch (selectedPreset.value) {
+ case '': return 'Sin filtro'
+ case 'custom': return 'Personalizado'
+ case 'hoy': return 'Hoy'
+ case 'semana': return 'Esta Semana'
+ case 'mes': return 'Este Mes'
+ case 'ytd': return 'YTD'
+ case 'cosecha-20-21': return 'Cosecha 20-21'
+ case 'cosecha-21-22': return 'Cosecha 21-22'
+ case 'cosecha-22-23': return 'Cosecha 22-23'
+ case 'cosecha-23-24': return 'Cosecha 23-24'
+ case 'cosecha-24-25': return 'Cosecha 24-25'
+ case 'cosecha-25-26': return 'Cosecha 25-26'
+ default: return 'Seleccionar rango'
+ }
+})
+
+const dropdownItems = computed(() => [
+ {
+ label: 'Sin filtro',
+ click: () => selectPreset('')
+ },
+ {
+ label: 'Rápidos',
+ children: [
+ { label: 'Hoy', click: () => selectPreset('hoy') },
+ { label: 'Esta Semana', click: () => selectPreset('semana') },
+ { label: 'Este Mes', click: () => selectPreset('mes') },
+ { label: 'YTD', click: () => selectPreset('ytd') }
+ ]
+ },
+ {
+ label: 'Cosechas',
+ children: [
+ { label: 'Cosecha 20-21 (25 Sep 2020)', click: () => selectPreset('cosecha-20-21') },
+ { label: 'Cosecha 21-22 (25 Sep 2021)', click: () => selectPreset('cosecha-21-22') },
+ { label: 'Cosecha 22-23 (25 Sep 2022)', click: () => selectPreset('cosecha-22-23') },
+ { label: 'Cosecha 23-24 (25 Sep 2023)', click: () => selectPreset('cosecha-23-24') },
+ { label: 'Cosecha 24-25 (25 Sep 2024)', click: () => selectPreset('cosecha-24-25') },
+ { label: 'Cosecha 25-26 (10 Sep 2025 → hoy)', click: () => selectPreset('cosecha-25-26') }
+ ]
+ }
+])
+
+// Fechas (YYYY-MM-DD) — Honduras (UTC-6)
+const toLocalDateStr = (d: Date) => {
+ const y = d.getFullYear()
+ const m = String(d.getMonth() + 1).padStart(2,'0')
+ const day = String(d.getDate()).padStart(2,'0')
+ return `${y}-${m}-${day}`
+}
+const fechaDesde = ref(null)
+const fechaHasta = ref(null)
+
+function selectPreset(preset: PresetValue) {
+ selectedPreset.value = preset
+
+ if (preset === '' || preset === 'custom') {
+ fechaDesde.value = null
+ fechaHasta.value = null
+ return
+ }
+
+ const now = new Date()
+ const set = (sd: string | null, ed: string | null) => {
+ fechaDesde.value = sd
+ fechaHasta.value = ed
+ }
+
+ switch (preset) {
+ case 'hoy': set(toLocalDateStr(now), toLocalDateStr(now)); break
+ case 'semana': {
+ const d = new Date(now)
+ const day = d.getDay() || 7
+ d.setDate(d.getDate() - (day - 1)) // lunes
+ set(toLocalDateStr(d), toLocalDateStr(now))
+ break
+ }
+ case 'mes': set(toLocalDateStr(new Date(now.getFullYear(), now.getMonth(), 1)), toLocalDateStr(now)); break
+ case 'ytd': set(toLocalDateStr(new Date(now.getFullYear(), 0, 1)), toLocalDateStr(now)); break
+ case 'cosecha-20-21': set('2020-09-25', '2021-09-24'); break
+ case 'cosecha-21-22': set('2021-09-25', '2022-09-24'); break
+ case 'cosecha-22-23': set('2022-09-25', '2023-09-24'); break
+ case 'cosecha-23-24': set('2023-09-25', '2024-09-24'); break
+ case 'cosecha-24-25': set('2024-09-25', '2025-09-09'); break
+ case 'cosecha-25-26': set('2025-09-10', toLocalDateStr(now)); break
+ }
+}
+
+function onManualDateChange() {
+ // Si el usuario modifica las fechas manualmente, cambiar a "Personalizado"
+ selectedPreset.value = 'custom'
+}
+
+function clearPreset() {
+ selectedPreset.value = ''
+ fechaDesde.value = null
+ fechaHasta.value = null
+}
+
+const rangoLegible = computed(() => {
+ if (!fechaDesde.value && !fechaHasta.value) return 'Sin filtro de fecha'
+ const f = fechaDesde.value ?? '—'
+ const t = fechaHasta.value ?? '—'
+ return `${f} → ${t}`
+})
+
+function isAnulado(row: any): boolean {
+ const estado = (row?.estado ?? '').toString().toLowerCase()
+ const fechaAn = row?.fecha_anulado ?? null
+ return estado === 'anulado' || !!fechaAn
+}
+
+function isWithinDate(row: any, from?: string | null, to?: string | null): boolean {
+ const created = row?.created_at ? new Date(row.created_at) : null
+ if (!created || isNaN(created.getTime())) return false
+ if (from) {
+ const fd = new Date(from + 'T00:00:00-06:00')
+ if (created < fd) return false
+ }
+ if (to) {
+ const td = new Date(to + 'T23:59:59-06:00')
+ if (created > td) return false
+ }
+ return true
+}
+
+// Filtrados que alimentan los métricos
+const ingresosFiltrados = computed(() => {
+ return (ingresos.value ?? [])
+ .filter(r => (includeAnulados.value ? true : !isAnulado(r)))
+ .filter(r => isWithinDate(r, fechaDesde.value, fechaHasta.value))
+})
+
+const rechazosFiltrados = computed(() => {
+ return (rechazos.value ?? [])
+ .filter(r => (includeAnulados.value ? true : !isAnulado(r)))
+ .filter(r => isWithinDate(r, fechaDesde.value, fechaHasta.value))
+})
+
+// Métricos basados en filtrados
+const ingresosMetrics = useIngresosMetrics(ingresosFiltrados)
+const rechazosMetrics = useRechazosMetrics(rechazosFiltrados)
// Loading and error states
const loading = computed(() => ingresosStore.isLoading || rechazosStore.isLoading)
@@ -133,16 +355,9 @@ const loadingProgress = ref(0)
const lastUpdated = computed(() => {
const ingresosDate = ingresosStore.lastUpdated
const rechazosDate = rechazosStore.lastUpdated
-
if (!ingresosDate && !rechazosDate) return 'Nunca'
-
- const latest = [ingresosDate, rechazosDate]
- .filter(Boolean)
- .sort()
- .reverse()[0]
-
+ const latest = [ingresosDate, rechazosDate].filter(Boolean).sort().reverse()[0] as string | undefined
if (!latest) return 'Nunca'
-
return new Date(latest).toLocaleString('es-ES', {
year: 'numeric',
month: 'long',
@@ -166,21 +381,13 @@ const formatCurrency = (value: number) => {
const metadataStore = useMetadataStore()
const ingresosMetadata = computed(() => {
- // Buscar por el nombre de la vista
- const meta = metadataStore.allTables.find(t => t.table === 'vista_detalle_ingresos')
- return meta ? {
- ...meta,
- name: 'ingresos'
- } : null
+ const meta = metadataStore.metadata.find((t: any) => t.table === 'vista_detalle_ingresos')
+ return meta ? { ...meta, name: 'ingresos' } : null
})
const rechazosMetadata = computed(() => {
- // Buscar por el nombre de la tabla de rechazos
- const meta = metadataStore.allTables.find(t => t.table === 'rechazos')
- return meta ? {
- ...meta,
- name: 'rechazos'
- } : null
+ const meta = metadataStore.metadata.find((t: any) => t.table === 'rechazos')
+ return meta ? { ...meta, name: 'rechazos' } : null
})
// Refresh data
@@ -209,21 +416,21 @@ async function refreshData() {
}
}
-// Load data on mount
+// Load data on mount + default preset
onMounted(async () => {
try {
// Cargar metadatos primero
- if (!metadataStore.hasMetadata) {
- await metadataStore.loadMetadata()
+ if (metadataStore.metadata.length === 0) {
+ await metadataStore.fetchMetadata()
}
- // Primero cargamos del cache para mostrar datos inmediatamente
+ // Cache primero para UX
await Promise.all([
ingresosStore.loadFromCache(),
rechazosStore.loadFromCache()
])
- // Si no hay datos en cache o están desactualizados, cargamos todos los datos
+ // Si falta data, cargar en lotes
if (!ingresosStore.hasData || !rechazosStore.hasData) {
loadingProgress.value = 0
let ingresosProgress = 0
@@ -244,6 +451,10 @@ onMounted(async () => {
}
} catch (err) {
console.error('Error loading data:', err)
+ } finally {
+ // Default preset: cosecha 25-26
+ selectPreset('cosecha-25-26')
+ includeAnulados.value = false
}
})
-
\ No newline at end of file
+