Implementar sistema completo de trazabilidad de lotes
Some checks failed
build-and-deploy / build-and-deploy (push) Failing after 1m47s

- Agregar PostgreSQL 16 con esquema completo
- Crear API endpoints para lotes y operaciones
- Implementar UI con Nuxt UI (tablas, formularios, trazabilidad)
- Agregar datos de ejemplo del flujo completo
- Documentar sistema en PLAN_TRAZABILIDAD.md
This commit is contained in:
2025-11-21 18:39:04 -06:00
parent e5456bf522
commit ee3dffa38e
26 changed files with 4223 additions and 74 deletions

View File

@@ -6,85 +6,45 @@
<UContainer class="py-8">
<div class="space-y-6">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold mb-2">Plantilla Nuxt + Authentik</h1>
<p class="text-gray-600 dark:text-gray-400">
Ejemplo de integración con Authentik Proxy Outpost
</p>
<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>
<!-- Componentes de autenticación -->
<div v-if="isAuthenticated" class="grid gap-6 lg:grid-cols-2">
<!-- Columna izquierda -->
<div class="space-y-6">
<!-- Avatar y datos básicos -->
<AuthUserAvatar />
<!-- Botones de acción individuales -->
<UCard class="w-full">
<template #header>
<h3 class="text-lg font-semibold">Acciones de Sesión</h3>
</template>
<div class="flex flex-wrap gap-3">
<AuthSessionStatusButton />
<AuthProfileButton />
<AuthLogoutButton />
<AuthLoginButton />
<!-- 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>
</UCard>
</template>
<!-- Verificaciones Frontend/Backend -->
<UCard class="w-full">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-cpu-chip" class="w-5 h-5" />
<h3 class="text-lg font-semibold">Verificación de Sistema</h3>
</div>
</template>
<div class="flex flex-wrap gap-3">
<AuthFrontendVerificationButton />
<AuthBackendVerificationButton />
<!-- Tab: Operaciones -->
<template #operaciones>
<div class="py-4">
<OperacionesOperacionesTable
@create="showCreateOperacionModal = true"
@view="handleViewOperacion"
/>
</div>
</UCard>
</div>
<!-- Columna derecha -->
<div class="space-y-6">
<!-- Metadatos completos -->
<AuthUserMetadata />
<!-- Verificaciones de Grupos Frontend -->
<UCard class="w-full">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-user-group" class="w-5 h-5 text-purple-500" />
<h3 class="text-lg font-semibold">Grupos (Frontend)</h3>
</div>
</template>
<div class="grid grid-cols-2 gap-3">
<AuthCheckAuthentikAdminsButton />
<AuthCheckGrupoPruebaButton />
<AuthCheckLvl0Button />
<AuthCheckPublicAccessButton />
</div>
</UCard>
<!-- Verificaciones de Grupos Backend -->
<UCard class="w-full">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-server-stack" class="w-5 h-5 text-orange-500" />
<h3 class="text-lg font-semibold">Grupos (Backend)</h3>
</div>
</template>
<div class="grid grid-cols-2 gap-3">
<AuthCheckAuthentikAdminsButton :verify-backend="true" />
<AuthCheckGrupoPruebaButton :verify-backend="true" />
<AuthCheckLvl0Button :verify-backend="true" />
<AuthCheckPublicAccessButton :verify-backend="true" />
</div>
</UCard>
</div>
</template>
</UTabs>
</div>
<!-- Mensaje si no está autenticado -->
@@ -99,14 +59,130 @@
</UCard>
</div>
</UContainer>
<!-- Modal: Crear/Editar Lote -->
<UModal v-model="showLoteFormModal">
<LotesLoteForm
:lote="selectedLote"
@cancel="closeLoteFormModal"
@success="handleLoteFormSuccess"
/>
</UModal>
<!-- Modal: Ver Detalle de Lote -->
<UModal v-model="showLoteDetailModal">
<LotesLoteCard
v-if="selectedLote"
:lote="selectedLote"
@edit="handleEditLoteFromDetail"
@trazabilidad="handleViewTrazabilidadFromDetail"
/>
</UModal>
<!-- Modal: Ver Trazabilidad -->
<UModal v-model="showTrazabilidadModal" :ui="{ width: 'max-w-4xl' }">
<LotesTrazabilidadTree
v-if="trazabilidadLoteId"
:lote-id="trazabilidadLoteId"
@close="showTrazabilidadModal = false"
/>
</UModal>
<!-- Modal: Crear Operación -->
<UModal v-model="showCreateOperacionModal" :ui="{ width: 'max-w-3xl' }">
<OperacionesOperacionForm
@cancel="showCreateOperacionModal = false"
@success="handleOperacionFormSuccess"
/>
</UModal>
</UApp>
</template>
<script setup lang="ts">
import type { Lote, Operacion } from '~/composables/useLotes'
const { isAuthenticated } = useAuthentik()
// Navegación
const selectedTab = ref(0)
const tabs = [
{ label: 'Lotes', icon: 'i-heroicons-cube', slot: 'lotes' },
{ label: 'Operaciones', icon: 'i-heroicons-beaker', slot: '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
}
// 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' },