/** * 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 => 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 } 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> { 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 { 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> { const nombres = new Set() 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 { const valores: Record = {} 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 { 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) })