- Motor de cálculo parametrizado por desarrollo (enganches, descuentos, splits, fuente de N, quirk Infonavit-lista); DIAGONAL byte-idéntico - 285 unidades (5 desarrollos) en seeds, API (?desarrollo=) y board Monday - Selector de desarrollo en UI, propuestas por config, PDF multi-desarrollo - Knowledge IA: catálogo por desarrollo + matriz de diferencias (10 docs) - Tests: cotizador 53, api 99 — fixtures de los Excel validados al centavo
190 lines
6.9 KiB
TypeScript
190 lines
6.9 KiB
TypeScript
/**
|
|
* Importa los inventarios de ATELIER, ESTANZA, SENDA y SOLANA al board
|
|
* "Unidades" de Monday, desde reference/cotizadores/{DEV}.json.
|
|
*
|
|
* - Idempotente: salta items cuyo nombre (código prefijado) ya existe.
|
|
* - Crea la columna "Meses para firma" (numbers) si falta (la usa SOLANA).
|
|
* - create_labels_if_missing: la columna status "Disponibilidad" gana las
|
|
* etiquetas nuevas (VENDIDO, ESCRITURADO, APARTADO, BLOQUEADO).
|
|
* - Delay entre mutaciones para cuidar el rate limit.
|
|
*
|
|
* Uso: npx tsx scripts/importar-unidades.ts (desde api-gateway/)
|
|
*/
|
|
import { readFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { config } from '../src/config/index.js'
|
|
import { MondayService } from '../src/services/monday.service.js'
|
|
|
|
const REF_DIR = path.resolve(process.cwd(), '..', 'reference', 'cotizadores')
|
|
const DELAY_MS = 400
|
|
|
|
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
|
|
|
|
interface UnidadJson {
|
|
codigo: string
|
|
modelo: string | null
|
|
fechaEntrega: string | null
|
|
supEstacionamiento: number | null
|
|
supConstruccion: number | null
|
|
supExcedente: number | null
|
|
status: string
|
|
extras: Record<string, string | number | null>
|
|
}
|
|
|
|
interface DefinicionDesarrollo {
|
|
id: string
|
|
archivo: string
|
|
codigoMonday: (u: UnidadJson) => string
|
|
precio: (u: UnidadJson) => number | null
|
|
mesesParaFirma?: (u: UnidadJson) => number | null
|
|
}
|
|
|
|
const DESARROLLOS: DefinicionDesarrollo[] = [
|
|
{
|
|
id: 'ATELIER',
|
|
archivo: 'ATELIER.json',
|
|
codigoMonday: (u) => `ATELIER-${u.codigo}`,
|
|
precio: (u) => (typeof u.extras['precioLista'] === 'number' ? u.extras['precioLista'] : null),
|
|
},
|
|
{
|
|
id: 'ESTANZA',
|
|
archivo: 'ESTANZA.json',
|
|
codigoMonday: (u) => `ESTANZA-${u.codigo.replace(/^CASA-/, '')}`,
|
|
precio: (u) => (typeof u.extras['precioLista'] === 'number' ? u.extras['precioLista'] : null),
|
|
},
|
|
{
|
|
id: 'SENDA',
|
|
archivo: 'SENDA.json',
|
|
codigoMonday: (u) => `SENDA-${u.codigo}`,
|
|
precio: (u) => (typeof u.extras['precioPreventa'] === 'number' ? u.extras['precioPreventa'] : null),
|
|
},
|
|
{
|
|
id: 'SOLANA',
|
|
archivo: 'SOLANA.json',
|
|
codigoMonday: (u) => `SOLANA-${u.codigo}`,
|
|
precio: (u) => (typeof u.extras['precioPreventa'] === 'number' ? u.extras['precioPreventa'] : null),
|
|
mesesParaFirma: (u) =>
|
|
typeof u.extras['mesesParaFirma'] === 'number' ? u.extras['mesesParaFirma'] : null,
|
|
},
|
|
]
|
|
|
|
const svc = new MondayService()
|
|
const boardId = config.monday.boards.unidades
|
|
|
|
/** Columnas del board mapeadas por título */
|
|
async function columnasBoard(): Promise<Map<string, { id: string; title: string; type: string }>> {
|
|
const data = await svc.graphql<{ boards: { columns: { id: string; title: string; type: string }[] }[] }>(
|
|
`query ($ids: [ID!]) { boards(ids: $ids) { columns { id title type } } }`,
|
|
{ ids: [boardId] },
|
|
)
|
|
return new Map((data.boards[0]?.columns ?? []).map((c) => [c.title, c]))
|
|
}
|
|
|
|
async function asegurarColumnaMeses(): Promise<void> {
|
|
const cols = await columnasBoard()
|
|
if (cols.has('Meses para firma')) return
|
|
const data = await svc.graphql<{ create_column: { id: string } }>(
|
|
`mutation ($boardId: ID!, $title: String!) {
|
|
create_column(board_id: $boardId, title: $title, column_type: numbers) { id }
|
|
}`,
|
|
{ boardId, title: 'Meses para firma' },
|
|
)
|
|
console.log(`Columna "Meses para firma" creada (${data.create_column.id})`)
|
|
}
|
|
|
|
/** Nombres de TODOS los items del board (paginado por cursor) */
|
|
async function itemsExistentes(): Promise<Set<string>> {
|
|
const nombres = new Set<string>()
|
|
let cursor: string | null = null
|
|
do {
|
|
const data: { boards: { items_page: { cursor: string | null; items: { name: string }[] } }[] } =
|
|
cursor === null
|
|
? await svc.graphql(
|
|
`query ($ids: [ID!]) { boards(ids: $ids) { items_page(limit: 500) { cursor items { name } } } }`,
|
|
{ ids: [boardId] },
|
|
)
|
|
: await svc.graphql(
|
|
`query ($ids: [ID!], $cursor: String!) {
|
|
boards(ids: $ids) { items_page(limit: 500, cursor: $cursor) { cursor items { name } } }
|
|
}`,
|
|
{ ids: [boardId], cursor },
|
|
)
|
|
const page = data.boards[0]?.items_page
|
|
if (!page) break
|
|
for (const item of page.items) nombres.add(item.name)
|
|
cursor = page.cursor
|
|
} while (cursor !== null)
|
|
return nombres
|
|
}
|
|
|
|
function columnValuesDe(
|
|
def: DefinicionDesarrollo,
|
|
u: UnidadJson,
|
|
cols: Map<string, { id: string; title: string; type: string }>,
|
|
): string {
|
|
const valores: Record<string, unknown> = {}
|
|
const poner = (titulo: string, valor: unknown) => {
|
|
const col = cols.get(titulo)
|
|
if (!col) throw new Error(`Columna "${titulo}" no existe en el board`)
|
|
valores[col.id] = valor
|
|
}
|
|
const precio = def.precio(u)
|
|
poner('Desarrollo', def.id)
|
|
poner('Modelo', u.modelo ?? '')
|
|
if (u.fechaEntrega) poner('Fecha entrega', { date: u.fechaEntrega })
|
|
if (u.supConstruccion !== null) poner('Sup. construcción', u.supConstruccion)
|
|
if (u.supExcedente !== null) poner('Sup. excedente', u.supExcedente)
|
|
if (u.supEstacionamiento !== null) poner('Sup. estacionamiento', u.supEstacionamiento)
|
|
if (precio !== null) {
|
|
// Desarrollos de precio único: ambos precios replican el precio de lista
|
|
poner('Precio sin acabados', precio)
|
|
poner('Precio con acabados', precio)
|
|
}
|
|
poner('Disponibilidad', { label: u.status })
|
|
const meses = def.mesesParaFirma?.(u)
|
|
if (meses !== null && meses !== undefined) poner('Meses para firma', meses)
|
|
return JSON.stringify(valores)
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
await asegurarColumnaMeses()
|
|
const cols = await columnasBoard()
|
|
const existentes = await itemsExistentes()
|
|
console.log(`Board ${boardId}: ${existentes.size} items existentes`)
|
|
|
|
for (const def of DESARROLLOS) {
|
|
const raw = JSON.parse(await readFile(path.join(REF_DIR, def.archivo), 'utf8')) as {
|
|
unidades: UnidadJson[]
|
|
}
|
|
let creados = 0
|
|
let saltados = 0
|
|
for (const u of raw.unidades) {
|
|
const nombre = def.codigoMonday(u)
|
|
if (existentes.has(nombre)) {
|
|
saltados++
|
|
continue
|
|
}
|
|
const columnValues = columnValuesDe(def, u, cols)
|
|
await svc.graphql<{ create_item: { id: string } }>(
|
|
`mutation ($boardId: ID!, $itemName: String!, $columnValues: JSON!) {
|
|
create_item(board_id: $boardId, item_name: $itemName, column_values: $columnValues, create_labels_if_missing: true) { id }
|
|
}`,
|
|
{ boardId, itemName: nombre, columnValues },
|
|
)
|
|
existentes.add(nombre)
|
|
creados++
|
|
if (creados % 25 === 0) console.log(` ${def.id}: ${creados} creados…`)
|
|
await sleep(DELAY_MS)
|
|
}
|
|
console.log(`${def.id}: ${creados} creados, ${saltados} ya existían (total ${raw.unidades.length})`)
|
|
}
|
|
svc.invalidarCacheUnidades()
|
|
console.log('Importación terminada.')
|
|
process.exit(0)
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Falló la importación:', err instanceof Error ? err.message : err)
|
|
process.exit(1)
|
|
})
|