Files
seguidorDeLotes/nuxt4/app/composables/useLotes.ts
josedario87 f3a170c882
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
Feat: agregar 4 flujos complejos al seed y filtrar lotes finales en grafos
- Agregar flujo 5: Las Nubes (Geisha) + El Paraíso (Castillo) → SEC-EXOTIC-001
- Agregar flujo 6: Santa Rita (Caturra Rojo) → División → SEC-SRT-PREM-001 + SEC-SRT-STD-001
- Agregar flujo 7: Trinidad + San José + Villa Rosa → Triple Blend → SEC-TRIPLE-001
- Agregar flujo 8: Mezcla de segundas calidades → SEC-COMERCIAL-001
- Implementar filtro soloFinales en queries, API y composable
- Modificar tab Grafos para mostrar solo lotes finales (sin hijos)
- Actualizar descripción de tab Grafos para clarificar el filtro
- Total: 7 lotes finales de secado para visualización de grafos
2025-11-22 04:05:24 -06:00

404 lines
10 KiB
TypeScript

/**
* Composable para gestión de lotes y operaciones de trazabilidad
*/
export interface Lote {
id: string
codigo: string | null
tipo: string
fecha_creado: string
lugar_id: number | null
cantidad_kg: number | null
meta: Record<string, any> | null
}
export interface Operacion {
id: string
tipo: string
fecha: string
lugar_id: number | null
meta: Record<string, any> | null
}
export interface TrazabilidadRow {
lote_id: string
codigo: string | null
tipo: string
cantidad_kg: number | null
operacion_id: string | null
operacion_tipo: string | null
profundidad: number
parent_lote_id: string | null
}
export const useLotes = () => {
console.log('🔵 useLotes: Composable llamado, process.client:', process.client)
// useToast solo funciona en el cliente, no en SSR
const toast = process.client ? useToast() : null
console.log('🔵 useLotes: Toast inicializado:', !!toast)
// =====================================================
// FUNCIONES PARA LOTES
// =====================================================
/**
* Obtiene todos los lotes con filtros opcionales
*/
const fetchLotes = async (filtros?: {
tipo?: string
limit?: number
offset?: number
soloFinales?: boolean
}) => {
console.log('🔵 fetchLotes: Iniciando, filtros:', filtros)
try {
const query = new URLSearchParams()
if (filtros?.tipo) query.append('tipo', filtros.tipo)
if (filtros?.limit) query.append('limit', filtros.limit.toString())
if (filtros?.offset) query.append('offset', filtros.offset.toString())
if (filtros?.soloFinales) query.append('soloFinales', 'true')
const queryString = query.toString()
const url = `/api/lotes${queryString ? `?${queryString}` : ''}`
console.log('🔵 fetchLotes: URL construida:', url)
console.log('🔵 fetchLotes: Llamando a useFetch...')
const { data, error } = await useFetch<{
success: boolean
data: Lote[]
count: number
}>(url)
console.log('🔵 fetchLotes: useFetch completado, data:', !!data.value, 'error:', !!error.value)
if (error.value) {
throw new Error(error.value.message || 'Error obteniendo lotes')
}
return data.value?.data || []
} catch (err: any) {
console.error('Error fetching lotes:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error obteniendo lotes',
color: 'red',
})
return []
}
}
/**
* Obtiene un lote por su ID
*/
const fetchLoteById = async (id: string) => {
try {
const { data, error } = await useFetch<{
success: boolean
data: Lote
}>(`/api/lotes/${id}`)
if (error.value) {
throw new Error(error.value.message || 'Error obteniendo lote')
}
return data.value?.data || null
} catch (err: any) {
console.error('Error fetching lote:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error obteniendo lote',
color: 'red',
})
return null
}
}
/**
* Crea un nuevo lote
*/
const createLote = async (loteData: {
codigo?: string
tipo: string
cantidad_kg?: number
lugar_id?: number
meta?: Record<string, any>
}) => {
try {
const { data, error } = await useFetch<{
success: boolean
data: Lote
}>('/api/lotes', {
method: 'POST',
body: loteData,
})
if (error.value) {
throw new Error(error.value.message || 'Error creando lote')
}
toast?.add({
title: 'Éxito',
description: 'Lote creado correctamente',
color: 'green',
})
return data.value?.data || null
} catch (err: any) {
console.error('Error creating lote:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error creando lote',
color: 'red',
})
return null
}
}
/**
* Actualiza un lote existente
*/
const updateLote = async (
id: string,
updates: Partial<{
codigo: string | null
tipo: string
cantidad_kg: number | null
lugar_id: number | null
meta: Record<string, any> | null
}>
) => {
try {
const { data, error } = await useFetch<{
success: boolean
data: Lote
}>(`/api/lotes/${id}`, {
method: 'PATCH',
body: updates,
})
if (error.value) {
throw new Error(error.value.message || 'Error actualizando lote')
}
toast?.add({
title: 'Éxito',
description: 'Lote actualizado correctamente',
color: 'green',
})
return data.value?.data || null
} catch (err: any) {
console.error('Error updating lote:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error actualizando lote',
color: 'red',
})
return null
}
}
/**
* Elimina un lote
*/
const deleteLote = async (id: string) => {
try {
const { error } = await useFetch(`/api/lotes/${id}`, {
method: 'DELETE',
})
if (error.value) {
throw new Error(error.value.message || 'Error eliminando lote')
}
toast?.add({
title: 'Éxito',
description: 'Lote eliminado correctamente',
color: 'green',
})
return true
} catch (err: any) {
console.error('Error deleting lote:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error eliminando lote',
color: 'red',
})
return false
}
}
/**
* Obtiene el historial completo de trazabilidad de un lote
*/
const fetchTrazabilidad = async (id: string) => {
try {
const { data, error } = await useFetch<{
success: boolean
data: {
historial: TrazabilidadRow[]
estadisticas: {
total_ancestros: number
profundidad_maxima: number
kg_iniciales: number
}
}
}>(`/api/lotes/${id}/trazabilidad`)
if (error.value) {
throw new Error(error.value.message || 'Error obteniendo trazabilidad')
}
return data.value?.data || null
} catch (err: any) {
console.error('Error fetching trazabilidad:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error obteniendo trazabilidad',
color: 'red',
})
return null
}
}
// =====================================================
// FUNCIONES PARA OPERACIONES
// =====================================================
/**
* Obtiene todas las operaciones con filtros opcionales
*/
const fetchOperaciones = async (filtros?: {
tipo?: string
limit?: number
offset?: number
}) => {
try {
const query = new URLSearchParams()
if (filtros?.tipo) query.append('tipo', filtros.tipo)
if (filtros?.limit) query.append('limit', filtros.limit.toString())
if (filtros?.offset) query.append('offset', filtros.offset.toString())
const queryString = query.toString()
const url = `/api/operaciones${queryString ? `?${queryString}` : ''}`
const { data, error } = await useFetch<{
success: boolean
data: Operacion[]
count: number
}>(url)
if (error.value) {
throw new Error(error.value.message || 'Error obteniendo operaciones')
}
return data.value?.data || []
} catch (err: any) {
console.error('Error fetching operaciones:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error obteniendo operaciones',
color: 'red',
})
return []
}
}
/**
* Crea una nueva operación con sus lotes inputs/outputs
*/
const createOperacion = async (operacionData: {
tipo: string
fecha?: string
lugar_id?: number
meta?: Record<string, any>
inputs: Array<{ lote_id: string; cantidad_kg?: number }>
outputs: Array<{
codigo?: string
tipo: string
cantidad_kg?: number
meta?: Record<string, any>
}>
}) => {
try {
const { data, error } = await useFetch<{
success: boolean
data: {
operacion: Operacion
lotes_creados: Lote[]
}
}>('/api/operaciones', {
method: 'POST',
body: operacionData,
})
if (error.value) {
throw new Error(error.value.message || 'Error creando operación')
}
toast?.add({
title: 'Éxito',
description: 'Operación creada correctamente',
color: 'green',
})
return data.value?.data || null
} catch (err: any) {
console.error('Error creating operacion:', err)
toast?.add({
title: 'Error',
description: err.message || 'Error creando operación',
color: 'red',
})
return null
}
}
// =====================================================
// CONSTANTES ÚTILES
// =====================================================
const TIPOS_LOTE = [
{ value: 'uva', label: 'Uva' },
{ value: 'despulpado_primera', label: 'Despulpado Primera' },
{ value: 'despulpado_segunda', label: 'Despulpado Segunda' },
{ value: 'despulpado_rechazos', label: 'Despulpado Rechazos' },
{ value: 'oreado', label: 'Oreado' },
{ value: 'presecado', label: 'Presecado' },
{ value: 'reposo', label: 'Reposo' },
{ value: 'secado', label: 'Secado' },
]
const TIPOS_OPERACION = [
{ value: 'ingreso', label: 'Ingreso', icon: 'i-heroicons-arrow-down-tray' },
{ value: 'despulpado', label: 'Despulpado', icon: 'i-heroicons-beaker' },
{ value: 'oreado', label: 'Oreado', icon: 'i-heroicons-sun' },
{ value: 'presecado', label: 'Presecado', icon: 'i-heroicons-fire' },
{ value: 'reposo', label: 'Reposo', icon: 'i-heroicons-pause' },
{ value: 'secado', label: 'Secado', icon: 'i-heroicons-check-circle' },
{ value: 'traslado', label: 'Traslado', icon: 'i-heroicons-arrow-right' },
{ value: 'mezcla', label: 'Mezcla', icon: 'i-heroicons-beaker' },
{ value: 'ajuste_merma', label: 'Ajuste Merma', icon: 'i-heroicons-adjustments-horizontal' },
{ value: 'ajuste_cantidad', label: 'Ajuste Cantidad', icon: 'i-heroicons-calculator' },
{ value: 'ajuste_tipo', label: 'Ajuste Tipo', icon: 'i-heroicons-pencil' },
]
return {
// Lotes
fetchLotes,
fetchLoteById,
createLote,
updateLote,
deleteLote,
fetchTrazabilidad,
// Operaciones
fetchOperaciones,
createOperacion,
// Constantes
TIPOS_LOTE,
TIPOS_OPERACION,
}
}