Files
analiticaNucleo/nuxt4-app/app/components/comercios/TotalesMonetariosComercio.vue
josedario87 400b0ae5b4
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 53s
Refactor: Reorganizar componentes de comercios en carpeta específica
- Mover TotalesMonetariosComercio a components/comercios/
- Mover TotalesPesoComercio a components/comercios/
- Mover TablaComerciosResumen a components/comercios/
- Actualizar referencias en informe-comercios.vue con prefijos de carpeta
2025-11-06 13:46:18 -06:00

225 lines
9.8 KiB
Vue

<template>
<UCard class="brand-card border border-transparent">
<template #header>
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold brand-section-title">Totales Monetarios</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="space-y-6">
<!-- Inversión Total y Métricas Principales -->
<div>
<h3 class="text-lg font-semibold text-[var(--brand-primary)] mb-3">Inversión Total</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
<MetricBox label="Total Invertido" :value="formatCurrency(data.total_invertido)" color="green" />
<MetricBox label="Total QQ Seco" :value="`${formatNumber(data.total_qq_seco)} QQ`" />
<MetricBox label="Precio Promedio/QQ" :value="formatCurrency(data.precio_promedio_por_qq)" color="blue" />
<MetricBox label="Número de Comercios" :value="data.num_comercios?.toString() || '0'" />
</div>
</div>
<!-- Distribución de Pagos -->
<div>
<h3 class="text-lg font-semibold text-[var(--brand-primary)] mb-3">Distribución de Pagos</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-4 py-3">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Efectivo</div>
<div class="text-lg font-bold text-green-400">
{{ formatCurrency(data.total_efectivo) }}
</div>
<div class="text-xs text-[var(--brand-text-muted)] mt-1">
{{ calcularPorcentaje(data.total_efectivo, data.total_invertido) }}%
</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-4 py-3">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Depósito</div>
<div class="text-lg font-bold text-blue-400">
{{ formatCurrency(data.total_deposito) }}
</div>
<div class="text-xs text-[var(--brand-text-muted)] mt-1">
{{ calcularPorcentaje(data.total_deposito, data.total_invertido) }}%
</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-4 py-3">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Cheque</div>
<div class="text-lg font-bold text-purple-400">
{{ formatCurrency(data.total_cheque) }}
</div>
<div class="text-xs text-[var(--brand-text-muted)] mt-1">
{{ calcularPorcentaje(data.total_cheque, data.total_invertido) }}%
</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-4 py-3">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Total Verificación</div>
<div class="text-lg font-bold" :class="totalMatch ? 'text-green-500' : 'text-red-500'">
{{ formatCurrency(totalDistribucion) }}
</div>
<div class="text-xs mt-1" :class="totalMatch ? 'text-green-500' : 'text-red-500'">
{{ totalMatch ? '✓ Coincide' : '✗ No coincide' }}
</div>
</div>
</div>
</div>
<!-- Gráfica de Distribución -->
<div v-if="showChart" class="mt-6">
<h4 class="text-sm font-semibold text-[var(--brand-text)] mb-3">Visualización de Distribución</h4>
<div class="space-y-2">
<div class="flex items-center gap-3">
<span class="text-xs w-20 text-[var(--brand-text-muted)]">Efectivo</span>
<div class="flex-1 h-8 bg-[var(--brand-surface)] rounded-lg overflow-hidden border border-[var(--brand-border)]">
<div
class="h-full bg-green-500/60 flex items-center justify-end px-2 transition-all duration-500"
:style="{ width: `${calcularPorcentaje(data.total_efectivo, data.total_invertido)}%` }"
>
<span class="text-xs font-semibold text-white">{{ calcularPorcentaje(data.total_efectivo, data.total_invertido) }}%</span>
</div>
</div>
<span class="text-xs w-32 text-right text-[var(--brand-text)]">{{ formatCurrency(data.total_efectivo) }}</span>
</div>
<div class="flex items-center gap-3">
<span class="text-xs w-20 text-[var(--brand-text-muted)]">Depósito</span>
<div class="flex-1 h-8 bg-[var(--brand-surface)] rounded-lg overflow-hidden border border-[var(--brand-border)]">
<div
class="h-full bg-blue-500/60 flex items-center justify-end px-2 transition-all duration-500"
:style="{ width: `${calcularPorcentaje(data.total_deposito, data.total_invertido)}%` }"
>
<span class="text-xs font-semibold text-white">{{ calcularPorcentaje(data.total_deposito, data.total_invertido) }}%</span>
</div>
</div>
<span class="text-xs w-32 text-right text-[var(--brand-text)]">{{ formatCurrency(data.total_deposito) }}</span>
</div>
<div class="flex items-center gap-3">
<span class="text-xs w-20 text-[var(--brand-text-muted)]">Cheque</span>
<div class="flex-1 h-8 bg-[var(--brand-surface)] rounded-lg overflow-hidden border border-[var(--brand-border)]">
<div
class="h-full bg-purple-500/60 flex items-center justify-end px-2 transition-all duration-500"
:style="{ width: `${calcularPorcentaje(data.total_cheque, data.total_invertido)}%` }"
>
<span class="text-xs font-semibold text-white">{{ calcularPorcentaje(data.total_cheque, data.total_invertido) }}%</span>
</div>
</div>
<span class="text-xs w-32 text-right text-[var(--brand-text)]">{{ formatCurrency(data.total_cheque) }}</span>
</div>
</div>
</div>
</div>
</UCard>
</template>
<script setup lang="ts">
const props = defineProps<{
data: {
total_invertido: number
total_qq_seco: number
precio_promedio_por_qq: number
total_efectivo: number
total_deposito: number
total_cheque: number
num_comercios: number
}
contadores?: {
total_comercios?: number
comercios_filtrados?: number
total_clientes?: number
clientes_con_comercios_filtrados?: number
}
rangoLegible: string
lastUpdated: string
}>()
const showChart = ref(true)
// Computed
const totalDistribucion = computed(() => {
return (props.data.total_efectivo || 0) +
(props.data.total_deposito || 0) +
(props.data.total_cheque || 0)
})
const totalMatch = computed(() => {
const diff = Math.abs(totalDistribucion.value - (props.data.total_invertido || 0))
return diff < 0.01 // Tolerancia de 1 centavo por redondeo
})
const formatCurrency = (value: number) => {
if (!value) return 'L 0.00'
return new Intl.NumberFormat('es-HN', {
style: 'currency',
currency: 'HNL',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value).replace('HNL', 'L')
}
const formatNumber = (value: number) => {
if (!value) return '0.00'
return new Intl.NumberFormat('es-HN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value)
}
const calcularPorcentaje = (parte: number, total: number): string => {
if (!total || total === 0) return '0.0'
const porcentaje = (parte / total) * 100
return porcentaje.toFixed(1)
}
async function copiarTexto() {
const footer = `
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 RESUMEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 Rango: ${props.rangoLegible}
📦 Comercios: ${props.contadores?.comercios_filtrados || 0} de ${props.contadores?.total_comercios || 0} registros
👥 Clientes: ${props.contadores?.clientes_con_comercios_filtrados || 0} de ${props.contadores?.total_clientes || 0} clientes
🕐 Generado: ${props.lastUpdated}`
const texto = `💰 TOTALES MONETARIOS DE COMERCIOS
💵 INVERSIÓN TOTAL:
Total Invertido: ${formatCurrency(props.data.total_invertido)}
Total QQ Seco: ${formatNumber(props.data.total_qq_seco)} QQ
Precio Promedio/QQ: ${formatCurrency(props.data.precio_promedio_por_qq)}
Número de Comercios: ${props.data.num_comercios}
💳 DISTRIBUCIÓN DE PAGOS:
Efectivo: ${formatCurrency(props.data.total_efectivo)} (${calcularPorcentaje(props.data.total_efectivo, props.data.total_invertido)}%)
Depósito: ${formatCurrency(props.data.total_deposito)} (${calcularPorcentaje(props.data.total_deposito, props.data.total_invertido)}%)
Cheque: ${formatCurrency(props.data.total_cheque)} (${calcularPorcentaje(props.data.total_cheque, props.data.total_invertido)}%)
Total Distribución: ${formatCurrency(totalDistribucion.value)}
Verificación: ${totalMatch.value ? '✓ Coincide con total invertido' : '✗ No coincide con total invertido'}${footer}`
await navigator.clipboard.writeText(texto)
alert('✅ Totales Monetarios copiados al portapapeles')
}
async function copiarJSON() {
const json = JSON.stringify(props.data, null, 2)
await navigator.clipboard.writeText(json)
alert('✅ JSON copiado al portapapeles')
}
</script>