104 lines
4.0 KiB
JavaScript
104 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Regenera los catálogos de unidades de la base de conocimiento del asistente
|
|
* a partir del INVENTARIO VIVO (tabla Unidad), para que el chat nunca conteste
|
|
* con un catálogo viejo.
|
|
*
|
|
* Uso: node scripts/actualizar-catalogos-ia.js
|
|
* Requiere NAN_API_KEY (embeddings) en el .env del ecosistema.
|
|
*
|
|
* Idempotente: indexarDocumento re-embebe solo si el contenido cambió.
|
|
*/
|
|
|
|
import { PrismaClient } from '@prisma/client'
|
|
import { readFileSync } from 'fs'
|
|
import { indexarDocumento } from '../dist/services/ai.service.js'
|
|
|
|
const prisma = new PrismaClient()
|
|
|
|
// Carga el .env del ecosistema (NAN_API_KEY / DATABASE_URL) si no vienen del entorno
|
|
for (const ruta of ['../.env', '../api-gateway/.env']) {
|
|
try {
|
|
const env = readFileSync(new URL(ruta, import.meta.url), 'utf8')
|
|
for (const linea of env.split('\n')) {
|
|
const m = linea.match(/^([A-Z_]+)=(.*)$/)
|
|
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim().replace(/^["']|["']$/g, '')
|
|
}
|
|
} catch { /* ignorar */ }
|
|
}
|
|
|
|
const fmtMoneda = (n) => `$${Math.round(n).toLocaleString('es-MX')}`
|
|
|
|
/** Markdown del catálogo de un desarrollo: modelos, disponibilidad y precios */
|
|
function catalogoDe(desarrollo, unidades) {
|
|
const porModelo = new Map()
|
|
for (const u of unidades) {
|
|
const modelo = u.modelo || '(sin modelo)'
|
|
const lista = porModelo.get(modelo) ?? []
|
|
lista.push(u)
|
|
porModelo.set(modelo, lista)
|
|
}
|
|
const conteoEstatus = {}
|
|
for (const u of unidades) conteoEstatus[u.status] = (conteoEstatus[u.status] ?? 0) + 1
|
|
|
|
const lineas = [
|
|
`# Catálogo de unidades — Desarrollo ${desarrollo}`,
|
|
'',
|
|
`Inventario actualizado desde el sistema (${unidades.length} unidades).`,
|
|
'',
|
|
'## Disponibilidad por estatus',
|
|
...Object.entries(conteoEstatus).map(([estatus, n]) => `- ${estatus}: ${n} unidades`),
|
|
'',
|
|
'## Modelos y precios',
|
|
]
|
|
for (const [modelo, lista] of [...porModelo.entries()].sort()) {
|
|
const disponibles = lista.filter((u) => u.status === 'DISPONIBLE')
|
|
const precios = lista.map((u) => u.precioLista).filter((p) => p > 0)
|
|
const min = precios.length > 0 ? Math.min(...precios) : 0
|
|
const max = precios.length > 0 ? Math.max(...precios) : 0
|
|
lineas.push(
|
|
`- **${modelo}**: ${lista.length} unidades (${disponibles.length} disponibles). ` +
|
|
(precios.length > 0 ? `Precio de lista ${fmtMoneda(min)} a ${fmtMoneda(max)}.` : 'Sin precio capturado.'),
|
|
)
|
|
if (disponibles.length > 0) {
|
|
const codigos = disponibles.slice(0, 12).map((u) => u.codigo).join(', ')
|
|
lineas.push(` - Disponibles: ${codigos}${disponibles.length > 12 ? ` (y ${disponibles.length - 12} más)` : ''}`)
|
|
}
|
|
}
|
|
lineas.push(
|
|
'',
|
|
'## Notas para el asistente',
|
|
'- Los precios son de lista, sin acabados (cocina y closet se cotizan aparte).',
|
|
'- Para los números de una unidad concreta (enganche, mensualidades, pago final) usa la herramienta cotizar_unidad.',
|
|
'- Para disponibilidad exacta en este momento usa buscar_unidad o contar_unidades: este documento es un resumen.',
|
|
)
|
|
return lineas.join('\n')
|
|
}
|
|
|
|
async function main() {
|
|
const unidades = await prisma.unidad.findMany({ orderBy: [{ desarrollo: 'asc' }, { modelo: 'asc' }, { codigo: 'asc' }] })
|
|
if (unidades.length === 0) {
|
|
console.error('No hay unidades en el inventario local; nada que regenerar.')
|
|
process.exit(1)
|
|
}
|
|
const porDesarrollo = new Map()
|
|
for (const u of unidades) {
|
|
const lista = porDesarrollo.get(u.desarrollo) ?? []
|
|
lista.push(u)
|
|
porDesarrollo.set(u.desarrollo, lista)
|
|
}
|
|
|
|
console.log(`Regenerando catálogos de ${porDesarrollo.size} desarrollo(s)…`)
|
|
for (const [desarrollo, lista] of [...porDesarrollo.entries()].sort()) {
|
|
const r = await indexarDocumento(`Catálogo de unidades — Desarrollo ${desarrollo}`, catalogoDe(desarrollo, lista))
|
|
console.log(` ${r.estado.padEnd(12)} ${r.titulo} (${r.chunks} chunks)`)
|
|
}
|
|
console.log('Listo.')
|
|
await prisma.$disconnect()
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Falló:', err instanceof Error ? err.message : err)
|
|
process.exit(1)
|
|
})
|