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

@@ -450,3 +450,269 @@ export async function getEstadisticasLote(loteId: string): Promise<{
kg_iniciales: kgIniciales,
}
}
// =====================================================
// TIPOS PARA VINCULACIONES EXTERNAS
// =====================================================
export interface VinculacionExterna {
id: string
tipo_registro: 'ingreso' | 'carreta' | 'salida' | 'rechazo'
registro_id: number
lote_id: string
fecha_vinculacion: Date
usuario_id: string | null
observaciones: string | null
datos_cache: Record<string, any> | null
periodo_cosecha: string
}
export interface LoteConVinculaciones extends Lote {
ingresos_vinculados: number
carretas_vinculadas: number
salidas_vinculadas: number
rechazos_vinculados: number
total_vinculaciones: number
}
// =====================================================
// QUERIES PARA VINCULACIONES EXTERNAS
// =====================================================
/**
* Obtiene todas las vinculaciones con filtros opcionales
*/
export async function getVinculaciones(filtros?: {
tipo_registro?: string
lote_id?: string
periodo_cosecha?: string
limit?: number
offset?: number
}): Promise<VinculacionExterna[]> {
let sql = 'SELECT * FROM vinculaciones_externas WHERE 1=1'
const params: any[] = []
let paramCount = 1
if (filtros?.tipo_registro) {
sql += ` AND tipo_registro = $${paramCount}`
params.push(filtros.tipo_registro)
paramCount++
}
if (filtros?.lote_id) {
sql += ` AND lote_id = $${paramCount}`
params.push(filtros.lote_id)
paramCount++
}
if (filtros?.periodo_cosecha) {
sql += ` AND periodo_cosecha = $${paramCount}`
params.push(filtros.periodo_cosecha)
paramCount++
}
sql += ' ORDER BY fecha_vinculacion DESC'
if (filtros?.limit) {
sql += ` LIMIT $${paramCount}`
params.push(filtros.limit)
paramCount++
}
if (filtros?.offset) {
sql += ` OFFSET $${paramCount}`
params.push(filtros.offset)
}
const result = await query<VinculacionExterna>(sql, params)
return result.rows
}
/**
* Obtiene vinculaciones por lote
*/
export async function getVinculacionesByLote(loteId: string): Promise<VinculacionExterna[]> {
const result = await query<VinculacionExterna>(
'SELECT * FROM vinculaciones_externas WHERE lote_id = $1 ORDER BY tipo_registro, fecha_vinculacion DESC',
[loteId]
)
return result.rows
}
/**
* Obtiene IDs de registros ya vinculados por tipo
*/
export async function getRegistrosVinculados(
tipo: string,
periodo: string = '25-26'
): Promise<number[]> {
const result = await query<{ registro_id: number }>(
'SELECT registro_id FROM vinculaciones_externas WHERE tipo_registro = $1 AND periodo_cosecha = $2',
[tipo, periodo]
)
return result.rows.map(r => r.registro_id)
}
/**
* Crea una nueva vinculación
*/
export async function createVinculacion(data: {
tipo_registro: string
registro_id: number
lote_id: string
usuario_id?: string
observaciones?: string
datos_cache?: Record<string, any>
periodo_cosecha?: string
}): Promise<VinculacionExterna> {
const result = await query<VinculacionExterna>(
`INSERT INTO vinculaciones_externas
(tipo_registro, registro_id, lote_id, usuario_id, observaciones, datos_cache, periodo_cosecha)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[
data.tipo_registro,
data.registro_id,
data.lote_id,
data.usuario_id || null,
data.observaciones || null,
data.datos_cache ? JSON.stringify(data.datos_cache) : null,
data.periodo_cosecha || '25-26'
]
)
return result.rows[0]
}
/**
* Crea múltiples vinculaciones en una transacción
*/
export async function createVinculacionesMasivas(
vinculaciones: Array<{
tipo_registro: string
registro_id: number
lote_id: string
usuario_id?: string
observaciones?: string
datos_cache?: Record<string, any>
periodo_cosecha?: string
}>
): Promise<VinculacionExterna[]> {
const client = await getClient()
try {
await client.query('BEGIN')
const resultados: VinculacionExterna[] = []
for (const v of vinculaciones) {
const result = await client.query<VinculacionExterna>(
`INSERT INTO vinculaciones_externas
(tipo_registro, registro_id, lote_id, usuario_id, observaciones, datos_cache, periodo_cosecha)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[
v.tipo_registro,
v.registro_id,
v.lote_id,
v.usuario_id || null,
v.observaciones || null,
v.datos_cache ? JSON.stringify(v.datos_cache) : null,
v.periodo_cosecha || '25-26'
]
)
resultados.push(result.rows[0])
}
await client.query('COMMIT')
return resultados
} catch (error) {
await client.query('ROLLBACK')
throw error
} finally {
client.release()
}
}
/**
* Elimina una vinculación
*/
export async function deleteVinculacion(id: string): Promise<boolean> {
const result = await query(
'DELETE FROM vinculaciones_externas WHERE id = $1',
[id]
)
return (result.rowCount ?? 0) > 0
}
/**
* Obtiene lotes con conteo de vinculaciones
*/
export async function getLotesConVinculaciones(filtros?: {
tipo?: string
limit?: number
offset?: number
}): Promise<LoteConVinculaciones[]> {
let sql = 'SELECT * FROM vista_lotes_con_vinculaciones WHERE 1=1'
const params: any[] = []
let paramCount = 1
if (filtros?.tipo) {
sql += ` AND tipo = $${paramCount}`
params.push(filtros.tipo)
paramCount++
}
sql += ' ORDER BY fecha_creado DESC'
if (filtros?.limit) {
sql += ` LIMIT $${paramCount}`
params.push(filtros.limit)
paramCount++
}
if (filtros?.offset) {
sql += ` OFFSET $${paramCount}`
params.push(filtros.offset)
}
const result = await query<LoteConVinculaciones>(sql, params)
return result.rows
}
/**
* Obtiene estadísticas de vinculación para un período
*/
export async function getEstadisticasVinculacion(periodo: string = '25-26'): Promise<{
ingreso: { vinculados: number }
carreta: { vinculados: number }
salida: { vinculados: number }
rechazo: { vinculados: number }
total_vinculados: number
}> {
const result = await query<{ tipo_registro: string; count: string }>(
`SELECT tipo_registro, COUNT(*) as count
FROM vinculaciones_externas
WHERE periodo_cosecha = $1
GROUP BY tipo_registro`,
[periodo]
)
const stats = {
ingreso: { vinculados: 0 },
carreta: { vinculados: 0 },
salida: { vinculados: 0 },
rechazo: { vinculados: 0 },
total_vinculados: 0
}
for (const row of result.rows) {
const count = parseInt(row.count)
if (row.tipo_registro === 'ingreso') stats.ingreso.vinculados = count
else if (row.tipo_registro === 'carreta') stats.carreta.vinculados = count
else if (row.tipo_registro === 'salida') stats.salida.vinculados = count
else if (row.tipo_registro === 'rechazo') stats.rechazo.vinculados = count
stats.total_vinculados += count
}
return stats
}