Agregar sistema de vinculaciones con registros externos de Metabase
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m46s

- Nuevo schema BD para vinculaciones_externas con constraint único por período
- Cliente Metabase para consultar Ingresos, Carretas, Salidas y Rechazos
- Endpoints API para registros externos (/api/externos/*) y vinculaciones (/api/vinculaciones/*)
- Composable useRegistrosExternos con lógica de vinculación individual y masiva
- Componentes: TablaRegistros, ModalAsignar, ProgressDashboard
- Tab "Externos" en app.vue con sub-tabs y dashboard de progreso
- LotesCard.vue ahora muestra registros vinculados al lote
This commit is contained in:
2025-11-29 15:25:26 -06:00
parent 1c96b696fa
commit ce8bad68d5
38 changed files with 2987 additions and 1 deletions

View File

@@ -0,0 +1,131 @@
/**
* POST /api/vinculaciones
*
* Crea una o múltiples vinculaciones.
*
* Body para vinculación individual:
* {
* tipo_registro: 'ingreso' | 'carreta' | 'salida' | 'rechazo',
* registro_id: number,
* lote_id: string,
* observaciones?: string,
* datos_cache?: object
* }
*
* Body para vinculación masiva:
* {
* masivo: true,
* items: Array<{
* tipo_registro: string,
* registro_id: number,
* lote_id: string,
* observaciones?: string,
* datos_cache?: object
* }>
* }
*/
import { createVinculacion, createVinculacionesMasivas } from '../../utils/queries'
export default defineEventHandler(async (event) => {
try {
const body = await readBody(event)
// Obtener usuario de Authentik si está disponible
const headers = getHeaders(event)
const usuarioId = headers['x-authentik-username'] || headers['x-authentik-uid'] || null
// Validar body
if (!body) {
throw createError({
statusCode: 400,
statusMessage: 'Body requerido',
})
}
// Vinculación masiva
if (body.masivo && Array.isArray(body.items)) {
if (body.items.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'Se requiere al menos un item para vinculación masiva',
})
}
// Validar cada item
for (const item of body.items) {
if (!item.tipo_registro || !item.registro_id || !item.lote_id) {
throw createError({
statusCode: 400,
statusMessage: 'Cada item requiere tipo_registro, registro_id y lote_id',
})
}
}
const vinculaciones = await createVinculacionesMasivas(
body.items.map((item: any) => ({
tipo_registro: item.tipo_registro,
registro_id: item.registro_id,
lote_id: item.lote_id,
usuario_id: usuarioId,
observaciones: item.observaciones,
datos_cache: item.datos_cache,
periodo_cosecha: item.periodo_cosecha || '25-26',
}))
)
return {
success: true,
data: vinculaciones,
message: `${vinculaciones.length} vinculaciones creadas exitosamente`,
}
}
// Vinculación individual
if (!body.tipo_registro || !body.registro_id || !body.lote_id) {
throw createError({
statusCode: 400,
statusMessage: 'Se requiere tipo_registro, registro_id y lote_id',
})
}
const vinculacion = await createVinculacion({
tipo_registro: body.tipo_registro,
registro_id: body.registro_id,
lote_id: body.lote_id,
usuario_id: usuarioId,
observaciones: body.observaciones,
datos_cache: body.datos_cache,
periodo_cosecha: body.periodo_cosecha || '25-26',
})
return {
success: true,
data: vinculacion,
message: 'Vinculación creada exitosamente',
}
} catch (error: any) {
console.error('[API] Error creando vinculación:', error)
// Error de constraint único (registro ya vinculado)
if (error.code === '23505') {
throw createError({
statusCode: 409,
statusMessage: 'Este registro ya está vinculado a un lote en este período',
})
}
// Error de FK (lote no existe)
if (error.code === '23503') {
throw createError({
statusCode: 400,
statusMessage: 'El lote especificado no existe',
})
}
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || error.message || 'Error creando vinculación',
})
}
})