Feat: Agregar botones de copia a Panorama Facturador
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 48s

Implementa botones "Copiar Texto" y "Copiar JSON" en todos los
componentes de datos del Panorama Facturador:

Componentes actualizados:
- SecosVendidos: Exporta inventario y proyecciones de café seco
- RechazosSubproductos: Exporta tabla de rechazos con totales
- Totales Financieros: Exporta vista general financiera (directo en página)

Además:
- Los componentes de Totales (IngresoCompra, Monetarios, Verde)
  ahora reciben contadores y metadata para incluir footer
- Todos los textos incluyen footer con:
  - Rango de fechas
  - Ingresos y rechazos filtrados vs totales
  - Fecha de generación

Nota: Panorama no tiene datos de clientes, solo ingresos y rechazos.
This commit is contained in:
2025-10-30 17:13:21 -06:00
parent 4a837fbbb0
commit 5500c83f9f
3 changed files with 245 additions and 11 deletions

View File

@@ -1,8 +1,34 @@
<template>
<UCard class="brand-card border border-transparent">
<template #header>
<h2 class="text-xl font-bold brand-section-title">Rechazos y Subproductos</h2>
<p class="text-sm text-[var(--brand-text-muted)] mt-1">Resumen de rechazos por tipo</p>
<div class="flex items-center justify-between">
<div>
<h2 class="text-xl font-bold brand-section-title">Rechazos y Subproductos</h2>
<p class="text-sm text-[var(--brand-text-muted)] mt-1">Resumen de rechazos por tipo</p>
</div>
<div class="flex items-center gap-2">
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-lucide-copy"
@click="copiarTexto"
:disabled="!data || data.length === 0"
>
Copiar Texto
</UButton>
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-lucide-braces"
@click="copiarJSON"
:disabled="!data || data.length === 0"
>
Copiar JSON
</UButton>
</div>
</div>
</template>
<div v-if="data && data.length > 0" class="overflow-x-auto">
@@ -76,6 +102,14 @@ interface Rechazo {
const props = defineProps<{
data: Rechazo[]
conteos?: {
ingresos_total?: number
ingresos_filtrados?: number
rechazos_total?: number
rechazos_filtrados?: number
}
rangoLegible?: string
lastUpdated?: string
}>()
const totalRegistros = computed(() => {
@@ -115,4 +149,52 @@ const formatCurrency = (value: number) => {
maximumFractionDigits: 2
}).format(value).replace('HNL', 'L')
}
async function copiarTexto() {
if (!props.data || props.data.length === 0) return
const footer = props.rangoLegible && props.lastUpdated ? `
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 RESUMEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 Rango: ${props.rangoLegible}
📦 Ingresos: ${props.conteos?.ingresos_filtrados || 0} de ${props.conteos?.ingresos_total || 0} registros
📦 Rechazos: ${props.conteos?.rechazos_filtrados || 0} de ${props.conteos?.rechazos_total || 0} registros
🕐 Generado: ${props.lastUpdated}` : ''
const texto = `🗑️ RECHAZOS Y SUBPRODUCTOS
${props.data.map((rechazo) => `
📦 ${rechazo.tipo.toUpperCase()}:
Registros: ${rechazo.num_registros}
Cantidad Total: ${formatNumber(rechazo.total_cantidad)}
Total Cobrado: ${formatCurrency(rechazo.total_cobrado)}
Precio Promedio: ${formatCurrency(rechazo.precio_promedio)}
`).join('\n')}
📊 TOTAL:
Registros: ${totalRegistros.value}
Cantidad Total: ${formatNumber(totalCantidad.value)}
Total Cobrado: ${formatCurrency(totalCobrado.value)}
Precio Promedio General: ${formatCurrency(promedioGeneral.value)}${footer}`
await navigator.clipboard.writeText(texto)
alert('✅ Rechazos y Subproductos copiados al portapapeles')
}
async function copiarJSON() {
if (!props.data || props.data.length === 0) return
const json = JSON.stringify({
rechazos: props.data,
totales: {
registros: totalRegistros.value,
cantidad: totalCantidad.value,
cobrado: totalCobrado.value,
promedio_general: promedioGeneral.value
}
}, null, 2)
await navigator.clipboard.writeText(json)
alert('✅ JSON copiado al portapapeles')
}
</script>

View File

@@ -1,7 +1,29 @@
<template>
<UCard class="brand-card border border-transparent">
<template #header>
<h2 class="text-xl font-bold brand-section-title">Café Seco - Inventario y Proyecciones</h2>
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold brand-section-title">Café Seco - Inventario y Proyecciones</h2>
<div class="flex items-center gap-2">
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-lucide-copy"
@click="copiarTexto"
>
Copiar Texto
</UButton>
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-lucide-braces"
@click="copiarJSON"
>
Copiar JSON
</UButton>
</div>
</div>
</template>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
@@ -47,6 +69,14 @@ const props = defineProps<{
precio_compra_promedio_por_qq: number
margen_ganancia_por_qq: number
}
conteos?: {
ingresos_total?: number
ingresos_filtrados?: number
rechazos_total?: number
rechazos_filtrados?: number
}
rangoLegible?: string
lastUpdated?: string
}>()
const margenColor = computed(() => {
@@ -71,4 +101,32 @@ const formatCurrency = (value: number) => {
maximumFractionDigits: 2
}).format(value).replace('HNL', 'L')
}
async function copiarTexto() {
const footer = props.rangoLegible && props.lastUpdated ? `
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 RESUMEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 Rango: ${props.rangoLegible}
📦 Ingresos: ${props.conteos?.ingresos_filtrados || 0} de ${props.conteos?.ingresos_total || 0} registros
📦 Rechazos: ${props.conteos?.rechazos_filtrados || 0} de ${props.conteos?.rechazos_total || 0} registros
🕐 Generado: ${props.lastUpdated}` : ''
const texto = `☕ CAFÉ SECO - INVENTARIO Y PROYECCIONES
📊 DATOS:
QQ Seco por Vender: ${formatNumber(props.data.total_qq_seco_por_vender)} QQ
Precio Venta Promedio/QQ: ${formatCurrency(props.data.precio_venta_promedio_por_qq)}
Precio Compra Promedio/QQ: ${formatCurrency(props.data.precio_compra_promedio_por_qq)}
Margen de Ganancia/QQ: ${formatCurrency(props.data.margen_ganancia_por_qq)}${footer}`
await navigator.clipboard.writeText(texto)
alert('✅ Café Seco - Inventario y Proyecciones copiado al portapapeles')
}
async function copiarJSON() {
const json = JSON.stringify(props.data, null, 2)
await navigator.clipboard.writeText(json)
alert('✅ JSON copiado al portapapeles')
}
</script>

View File

@@ -187,9 +187,31 @@
<!-- Totales Financieros - Resumen Principal -->
<UCard class="brand-card border border-transparent">
<template #header>
<div>
<h2 class="text-2xl font-bold brand-section-title">Totales Financieros</h2>
<p class="text-sm text-[var(--brand-text-muted)] mt-1">Vista general de ingresos, inversiones y rechazos</p>
<div class="flex items-center justify-between">
<div>
<h2 class="text-2xl font-bold brand-section-title">Totales Financieros</h2>
<p class="text-sm text-[var(--brand-text-muted)] mt-1">Vista general de ingresos, inversiones y rechazos</p>
</div>
<div class="flex items-center gap-2">
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-lucide-copy"
@click="copiarTotalesFinancierosTexto"
>
Copiar Texto
</UButton>
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-lucide-braces"
@click="copiarTotalesFinancierosJSON"
>
Copiar JSON
</UButton>
</div>
</div>
</template>
@@ -222,13 +244,53 @@
</UCard>
<!-- Secciones de Ingresos -->
<SecosVendidos :data="data.secosVendidos" />
<TotalesIngresoCompra :data="data.ingresoCompra" />
<TotalesMonetarios :data="data.monetarios" />
<TotalesVerde :data="data.verde" />
<SecosVendidos
:data="data.secosVendidos"
:conteos="data.conteos"
:rango-legible="rangoLegible"
:last-updated="lastUpdated"
/>
<TotalesIngresoCompra
:data="data.ingresoCompra"
:contadores="{
total_ingresos: data.conteos?.ingresos_total,
ingresos_filtrados: data.conteos?.ingresos_filtrados,
total_clientes: 0,
clientes_con_ingresos_filtrados: 0
}"
:rango-legible="rangoLegible"
:last-updated="lastUpdated"
/>
<TotalesMonetarios
:data="data.monetarios"
:contadores="{
total_ingresos: data.conteos?.ingresos_total,
ingresos_filtrados: data.conteos?.ingresos_filtrados,
total_clientes: 0,
clientes_con_ingresos_filtrados: 0
}"
:rango-legible="rangoLegible"
:last-updated="lastUpdated"
/>
<TotalesVerde
:data="data.verde"
:contadores="{
total_ingresos: data.conteos?.ingresos_total,
ingresos_filtrados: data.conteos?.ingresos_filtrados,
total_clientes: 0,
clientes_con_ingresos_filtrados: 0
}"
:rango-legible="rangoLegible"
:last-updated="lastUpdated"
/>
<!-- Sección de Rechazos -->
<RechazosSubproductos :data="data.rechazos" />
<RechazosSubproductos
:data="data.rechazos"
:conteos="data.conteos"
:rango-legible="rangoLegible"
:last-updated="lastUpdated"
/>
</template>
</div>
</template>
@@ -297,6 +359,38 @@ const formatCurrency = (value: number) => {
}).format(value).replace('HNL', 'L')
}
// Funciones de copia para Totales Financieros
async function copiarTotalesFinancierosTexto() {
if (!data.value?.financieros) return
const footer = `
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 RESUMEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 Rango: ${rangoLegible.value}
📦 Ingresos: ${data.value.conteos?.ingresos_filtrados || 0} de ${data.value.conteos?.ingresos_total || 0} registros
📦 Rechazos: ${data.value.conteos?.rechazos_filtrados || 0} de ${data.value.conteos?.rechazos_total || 0} registros
🕐 Generado: ${lastUpdated.value}`
const texto = `💰 TOTALES FINANCIEROS
📊 VISTA GENERAL:
Total Invertido en Café: ${formatCurrency(data.value.financieros.total_invertido_cafe)}
Total Rechazos: ${formatCurrency(data.value.financieros.total_rechazos)}
Balance Neto: ${formatCurrency(data.value.financieros.balance_neto)}${footer}`
await navigator.clipboard.writeText(texto)
alert('✅ Totales Financieros copiados al portapapeles')
}
async function copiarTotalesFinancierosJSON() {
if (!data.value?.financieros) return
const json = JSON.stringify(data.value.financieros, null, 2)
await navigator.clipboard.writeText(json)
alert('✅ JSON copiado al portapapeles')
}
// Methods
async function loadData() {
// Prevenir múltiples peticiones simultáneas