Agregar sistema de vinculaciones con registros externos de Metabase
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m46s
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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user