All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
- Cambiar selectedTab de ref(0) a ref('lotes') para usar string
- Agregar propiedad value explícita a cada tab item
- Crear documento nuxt-ui-tips.md con documentación de cambios
- Los valores deben ser strings según API de v4
372 lines
11 KiB
Vue
372 lines
11 KiB
Vue
<template>
|
||
<UApp>
|
||
<NuxtRouteAnnouncer />
|
||
<UNotifications />
|
||
|
||
<UContainer class="py-8">
|
||
<div class="space-y-6">
|
||
<!-- Header -->
|
||
<div class="flex justify-between items-center mb-8">
|
||
<div>
|
||
<h1 class="text-3xl font-bold">Seguidor de Lotes</h1>
|
||
<p class="text-gray-600 dark:text-gray-400">
|
||
Sistema de trazabilidad de café
|
||
</p>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<AuthUserAvatar v-if="isAuthenticated" />
|
||
<AuthLogoutButton v-if="isAuthenticated" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Botones de Prueba API -->
|
||
<UCard class="mb-4">
|
||
<div class="flex gap-2 flex-wrap">
|
||
<UButton @click="testGetLotes" color="primary">
|
||
Probar GET /api/lotes
|
||
</UButton>
|
||
<UButton @click="testGetOperaciones" color="primary">
|
||
Probar GET /api/operaciones
|
||
</UButton>
|
||
<UButton @click="testGetTrazabilidad" color="primary">
|
||
Probar Trazabilidad
|
||
</UButton>
|
||
</div>
|
||
<p class="text-sm text-gray-500 mt-2">
|
||
Los resultados se mostrarán en la consola del navegador (F12)
|
||
</p>
|
||
</UCard>
|
||
|
||
<!-- ⚠️⚠️⚠️ BOTONES DE DEBUG - TEMPORALES ⚠️⚠️⚠️ -->
|
||
<!-- NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000 -->
|
||
<UCard class="mb-4 border-2 border-red-500 bg-red-50 dark:bg-red-950">
|
||
<div class="flex items-center gap-2 mb-3">
|
||
<UIcon name="i-heroicons-exclamation-triangle" class="w-6 h-6 text-red-600" />
|
||
<h3 class="text-lg font-bold text-red-600">
|
||
⚠️ DEBUG - BOTONES TEMPORALES ⚠️
|
||
</h3>
|
||
</div>
|
||
<p class="text-sm text-red-700 dark:text-red-400 mb-3">
|
||
<strong>ADVERTENCIA:</strong> Estos botones modifican la base de datos directamente.
|
||
<br />
|
||
<strong>NO ELIMINAR</strong> este código sin consultar a Dario/Draganel/nucleo000.
|
||
</p>
|
||
<div class="flex gap-2 flex-wrap">
|
||
<UButton
|
||
@click="resetDatabase"
|
||
color="red"
|
||
variant="solid"
|
||
:loading="resettingDB"
|
||
>
|
||
🗑️ BORRAR TODA LA BD
|
||
</UButton>
|
||
<UButton
|
||
@click="seedDatabase"
|
||
color="orange"
|
||
variant="solid"
|
||
:loading="seedingDB"
|
||
>
|
||
🌱 CARGAR DATOS DE EJEMPLO
|
||
</UButton>
|
||
</div>
|
||
<p class="text-xs text-red-600 dark:text-red-400 mt-2">
|
||
Resultados en consola (F12). Recarga la página después de usar estos botones.
|
||
</p>
|
||
</UCard>
|
||
<!-- ⚠️⚠️⚠️ FIN BOTONES DE DEBUG ⚠️⚠️⚠️ -->
|
||
|
||
<!-- Contenido principal -->
|
||
<div v-if="isAuthenticated">
|
||
<!-- Navegación por Tabs -->
|
||
<UTabs v-model="selectedTab" :items="tabs" class="mb-6">
|
||
<!-- Tab: Lotes -->
|
||
<template #lotes>
|
||
<div class="py-4">
|
||
<LotesLotesTable
|
||
@create="showCreateLoteModal = true"
|
||
@view="handleViewLote"
|
||
@edit="handleEditLote"
|
||
@trazabilidad="handleViewTrazabilidad"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Tab: Operaciones -->
|
||
<template #operaciones>
|
||
<div class="py-4">
|
||
<OperacionesOperacionesTable
|
||
@create="showCreateOperacionModal = true"
|
||
@view="handleViewOperacion"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</UTabs>
|
||
</div>
|
||
|
||
<!-- Mensaje si no está autenticado -->
|
||
<UCard v-else class="text-center">
|
||
<div class="py-8">
|
||
<UIcon name="i-heroicons-shield-exclamation" class="w-16 h-16 mx-auto mb-4 text-gray-400" />
|
||
<h2 class="text-2xl font-semibold mb-2">No autenticado</h2>
|
||
<p class="text-gray-600 dark:text-gray-400">
|
||
Authentik Proxy Outpost debería redirigirte automáticamente.
|
||
</p>
|
||
</div>
|
||
</UCard>
|
||
</div>
|
||
</UContainer>
|
||
|
||
<!-- Modal: Crear/Editar Lote -->
|
||
<UModal v-model:open="showLoteFormModal">
|
||
<template #content>
|
||
<LotesLoteForm
|
||
:lote="selectedLote"
|
||
@cancel="closeLoteFormModal"
|
||
@success="handleLoteFormSuccess"
|
||
/>
|
||
</template>
|
||
</UModal>
|
||
|
||
<!-- Modal: Ver Detalle de Lote -->
|
||
<UModal v-model:open="showLoteDetailModal">
|
||
<template #content>
|
||
<LotesLoteCard
|
||
v-if="selectedLote"
|
||
:lote="selectedLote"
|
||
@edit="handleEditLoteFromDetail"
|
||
@trazabilidad="handleViewTrazabilidadFromDetail"
|
||
/>
|
||
</template>
|
||
</UModal>
|
||
|
||
<!-- Modal: Ver Trazabilidad -->
|
||
<UModal
|
||
v-model:open="showTrazabilidadModal"
|
||
:ui="{ content: 'w-[calc(100vw-2rem)] max-w-4xl rounded-lg shadow-lg ring ring-default' }"
|
||
>
|
||
<template #content>
|
||
<LotesTrazabilidadTree
|
||
v-if="trazabilidadLoteId"
|
||
:lote-id="trazabilidadLoteId"
|
||
@close="showTrazabilidadModal = false"
|
||
/>
|
||
</template>
|
||
</UModal>
|
||
|
||
<!-- Modal: Crear Operación -->
|
||
<UModal
|
||
v-model:open="showCreateOperacionModal"
|
||
:ui="{ content: 'w-[calc(100vw-2rem)] max-w-3xl rounded-lg shadow-lg ring ring-default' }"
|
||
>
|
||
<template #content>
|
||
<OperacionesOperacionForm
|
||
@cancel="showCreateOperacionModal = false"
|
||
@success="handleOperacionFormSuccess"
|
||
/>
|
||
</template>
|
||
</UModal>
|
||
</UApp>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import type { Lote, Operacion } from '~/composables/useLotes'
|
||
|
||
const { isAuthenticated } = useAuthentik()
|
||
|
||
// Navegación
|
||
const selectedTab = ref('lotes')
|
||
const tabs = [
|
||
{ label: 'Lotes', icon: 'i-heroicons-cube', slot: 'lotes', value: 'lotes' },
|
||
{ label: 'Operaciones', icon: 'i-heroicons-beaker', slot: 'operaciones', value: 'operaciones' },
|
||
]
|
||
|
||
// Estados de modales
|
||
const showLoteFormModal = ref(false)
|
||
const showLoteDetailModal = ref(false)
|
||
const showTrazabilidadModal = ref(false)
|
||
const showCreateLoteModal = ref(false)
|
||
const showCreateOperacionModal = ref(false)
|
||
|
||
// Estados de datos
|
||
const selectedLote = ref<Lote | null>(null)
|
||
const trazabilidadLoteId = ref<string | null>(null)
|
||
|
||
// Handlers para Lotes
|
||
const handleViewLote = (lote: Lote) => {
|
||
selectedLote.value = lote
|
||
showLoteDetailModal.value = true
|
||
}
|
||
|
||
const handleEditLote = (lote: Lote) => {
|
||
selectedLote.value = lote
|
||
showLoteFormModal.value = true
|
||
}
|
||
|
||
const handleEditLoteFromDetail = () => {
|
||
showLoteDetailModal.value = false
|
||
showLoteFormModal.value = true
|
||
}
|
||
|
||
const handleViewTrazabilidad = (lote: Lote) => {
|
||
trazabilidadLoteId.value = lote.id
|
||
showTrazabilidadModal.value = true
|
||
}
|
||
|
||
const handleViewTrazabilidadFromDetail = () => {
|
||
if (selectedLote.value) {
|
||
showLoteDetailModal.value = false
|
||
trazabilidadLoteId.value = selectedLote.value.id
|
||
showTrazabilidadModal.value = true
|
||
}
|
||
}
|
||
|
||
const closeLoteFormModal = () => {
|
||
showLoteFormModal.value = false
|
||
selectedLote.value = null
|
||
}
|
||
|
||
const handleLoteFormSuccess = () => {
|
||
closeLoteFormModal()
|
||
// La tabla se recargará automáticamente
|
||
}
|
||
|
||
// Handlers para Operaciones
|
||
const handleViewOperacion = (operacion: Operacion) => {
|
||
// TODO: Implementar vista de detalle de operación si es necesario
|
||
console.log('Ver operación:', operacion)
|
||
}
|
||
|
||
const handleOperacionFormSuccess = () => {
|
||
showCreateOperacionModal.value = false
|
||
// Las tablas se recargarán automáticamente
|
||
}
|
||
|
||
// ⚠️⚠️⚠️ FUNCIONES DE DEBUG - TEMPORALES ⚠️⚠️⚠️
|
||
// NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000
|
||
const resettingDB = ref(false)
|
||
const seedingDB = ref(false)
|
||
|
||
const resetDatabase = async () => {
|
||
if (!confirm('⚠️ ADVERTENCIA: Esto BORRARÁ TODOS LOS DATOS de la base de datos.\n\n¿Estás seguro de continuar?')) {
|
||
return
|
||
}
|
||
|
||
console.log('🗑️ === RESETEANDO BASE DE DATOS ===')
|
||
resettingDB.value = true
|
||
|
||
try {
|
||
const response = await fetch('/api/debug/reset-database', {
|
||
method: 'POST',
|
||
})
|
||
const data = await response.json()
|
||
console.log('Status:', response.status)
|
||
console.log('Respuesta:', data)
|
||
|
||
if (data.success) {
|
||
alert('✅ Base de datos reseteada exitosamente.\n\nRecarga la página para ver los cambios.')
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error:', error)
|
||
alert('❌ Error reseteando la base de datos. Ver consola.')
|
||
} finally {
|
||
resettingDB.value = false
|
||
}
|
||
}
|
||
|
||
const seedDatabase = async () => {
|
||
console.log('🌱 === CARGANDO DATOS DE EJEMPLO ===')
|
||
seedingDB.value = true
|
||
|
||
try {
|
||
const response = await fetch('/api/debug/seed-database', {
|
||
method: 'POST',
|
||
})
|
||
const data = await response.json()
|
||
console.log('Status:', response.status)
|
||
console.log('Respuesta:', data)
|
||
|
||
if (data.success) {
|
||
alert('✅ Datos de ejemplo cargados exitosamente.\n\nRecarga la página para ver los cambios.')
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error:', error)
|
||
alert('❌ Error cargando datos. Ver consola.')
|
||
} finally {
|
||
seedingDB.value = false
|
||
}
|
||
}
|
||
// ⚠️⚠️⚠️ FIN FUNCIONES DE DEBUG ⚠️⚠️⚠️
|
||
|
||
// Funciones de prueba de API
|
||
const testGetLotes = async () => {
|
||
console.log('=== Probando GET /api/lotes ===')
|
||
try {
|
||
const response = await fetch('/api/lotes')
|
||
const data = await response.json()
|
||
console.log('Status:', response.status)
|
||
console.log('Datos recibidos:', data)
|
||
} catch (error) {
|
||
console.error('Error:', error)
|
||
}
|
||
}
|
||
|
||
const testGetOperaciones = async () => {
|
||
console.log('=== Probando GET /api/operaciones ===')
|
||
try {
|
||
const response = await fetch('/api/operaciones')
|
||
const data = await response.json()
|
||
console.log('Status:', response.status)
|
||
console.log('Datos recibidos:', data)
|
||
} catch (error) {
|
||
console.error('Error:', error)
|
||
}
|
||
}
|
||
|
||
const testGetTrazabilidad = async () => {
|
||
console.log('=== Probando Trazabilidad ===')
|
||
try {
|
||
// Primero obtener lotes para tener un ID
|
||
const lotesResponse = await fetch('/api/lotes')
|
||
const lotesData = await lotesResponse.json()
|
||
console.log('Lotes disponibles:', lotesData)
|
||
|
||
if (lotesData.data && lotesData.data.length > 0) {
|
||
const primerLoteId = lotesData.data[0].id
|
||
console.log('Obteniendo trazabilidad para lote:', primerLoteId)
|
||
|
||
const trazResponse = await fetch(`/api/lotes/${primerLoteId}/trazabilidad`)
|
||
const trazData = await trazResponse.json()
|
||
console.log('Status:', trazResponse.status)
|
||
console.log('Trazabilidad:', trazData)
|
||
}
|
||
} catch (error) {
|
||
console.error('Error:', error)
|
||
}
|
||
}
|
||
|
||
// Watch para crear lote
|
||
watch(showCreateLoteModal, (value) => {
|
||
if (value) {
|
||
selectedLote.value = null
|
||
showLoteFormModal.value = true
|
||
showCreateLoteModal.value = false
|
||
}
|
||
})
|
||
|
||
// Configurar meta tags para PWA
|
||
useHead({
|
||
title: 'Seguidor de Lotes - Trazabilidad de Café',
|
||
link: [
|
||
{ rel: 'manifest', href: '/manifest.webmanifest' },
|
||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
|
||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }
|
||
],
|
||
meta: [
|
||
{ name: 'theme-color', content: '#00DC82' },
|
||
{ name: 'mobile-web-app-capable', content: 'yes' },
|
||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'default' }
|
||
]
|
||
})
|
||
</script>
|