Files
FRASA-CAS/api-gateway/scripts/seed-desarrollos.js
Consult AS Technology d6548999bf feat(desarrollos): modulo de alta de desarrollos con inventario, reglas del cotizador por desarrollo y micrositio publico
Modulo nuevo (solo admin) para dar de alta desarrollos sin tocar codigo:
- CRUD de la ficha, modelos e inventario (alta manual y generador por rango)
- Reglas del cotizador por desarrollo, editables y persistidas en la BD
- El cotizador consume las reglas desde /desarrollos/reglas-cotizador y cae a
  las constantes con aviso cuando un desarrollo aun no tiene reglas guardadas
- Micrositio publico en /d/:clave (sin sesion) con ficha, modelos y
  disponibilidad; jamas expone datos de clientes ni cotizaciones

Integracion:
- La landing monta el modulo en el grid (clave 'desarrollos', solo admin) y el
  micrositio fuera del login; el rol manager NO accede (matriz y API con 403)
- La tabla de roles de Configuracion ahora lista todos los modulos
- Se alinea la FK de ReglaCotizadorDesarrollo con la BD viva (Desarrollo.id)

Verificado: 708 pruebas del API, 115 del cotizador y 89 de la landing en verde;
12/12 cotizaciones identicas antes y despues del cambio de reglas; prueba en
vivo del API con alta, modelos, inventario, reglas, micrositio y baja (30/30).
2026-09-21 09:52:47 +00:00

158 lines
6.2 KiB
JavaScript

/**
* Siembra el catálogo `Desarrollo` + `ModeloDesarrollo` + `ReglaCotizadorDesarrollo`
* desde la realidad actual del sistema:
*
* - Desarrollos y modelos: lo que hoy hay en la tabla `Unidad` (espejo del
* inventario), más SOLE y MUTUO, que existen en captura y altas pero todavía
* no tienen inventario local.
* - Reglas del cotizador: copiadas (replicadas, no importadas) de las
* constantes de apps/cotizador/src/data/desarrollos.ts, que es la config con
* la que hoy cotiza producción.
*
* Idempotente: upsert por `clave`. NUNCA pisa lo ya editado: si la ficha o las
* reglas existen, el seed no las toca (el admin manda).
*
* Uso: npm run seed:desarrollos (tsx: la fuente de las reglas es TypeScript)
*/
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import dotenv from 'dotenv'
import { PrismaClient } from '@prisma/client'
import { DESARROLLOS as PARAMETROS_COTIZADOR } from '../../apps/cotizador/src/data/desarrollos.ts'
const raizApi = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
dotenv.config({ path: [path.resolve(raizApi, '.env'), path.resolve(raizApi, '..', '.env')] })
const prisma = new PrismaClient()
/**
* Desarrollos sin inventario local que sí existen en el ecosistema (captura y
* altas de lead/afluencia/cliente). Los modelos de SOLE vienen de la lista
* real del alta de clientes (apps/alta-clientes/src/data/constantes.ts);
* MUTUO captura el modelo en texto libre, así que se siembra sin modelos.
*/
const SIN_INVENTARIO = [
{ clave: 'SOLE', nombre: 'Sole', tipo: 'VERTICAL', modelos: ['ALBA', 'HORIZONTE', 'OCASO'] },
{ clave: 'MUTUO', nombre: 'Mutuo', tipo: 'VERTICAL', modelos: [] },
]
const normalizar = (v) => String(v ?? '').trim().toUpperCase()
/** Nombre comercial: el del cotizador si existe; si no, la clave capitalizada */
function nombreComercial(clave) {
const delCotizador = PARAMETROS_COTIZADOR[clave]?.nombre
if (typeof delCotizador === 'string' && delCotizador !== '') return delCotizador
return clave.charAt(0) + clave.slice(1).toLowerCase()
}
async function main() {
const [porDesarrollo, modelosInventario] = await Promise.all([
prisma.unidad.groupBy({ by: ['desarrollo'], _count: { codigo: true } }),
prisma.unidad.groupBy({ by: ['desarrollo', 'modelo'], _count: { codigo: true } }),
])
if (porDesarrollo.length === 0) {
throw new Error('La tabla Unidad está vacía: corre primero `npm run seed:unidades`')
}
const claves = new Set(porDesarrollo.map((d) => normalizar(d.desarrollo)))
for (const extra of SIN_INVENTARIO) claves.add(extra.clave)
const modelosPorClave = new Map()
for (const fila of modelosInventario) {
const clave = normalizar(fila.desarrollo)
const modelo = String(fila.modelo ?? '').trim()
if (modelo === '') continue
if (!modelosPorClave.has(clave)) modelosPorClave.set(clave, new Set())
modelosPorClave.get(clave).add(modelo)
}
for (const extra of SIN_INVENTARIO) {
if (!modelosPorClave.has(extra.clave)) modelosPorClave.set(extra.clave, new Set())
for (const modelo of extra.modelos) modelosPorClave.get(extra.clave).add(modelo)
}
let fichasNuevas = 0
let fichasExistentes = 0
let modelosNuevos = 0
let reglasNuevas = 0
for (const clave of [...claves].sort()) {
const extra = SIN_INVENTARIO.find((e) => e.clave === clave)
const parametros = PARAMETROS_COTIZADOR[clave]
const previa = await prisma.desarrollo.findUnique({ where: { clave }, select: { id: true } })
const ficha = await prisma.desarrollo.upsert({
where: { clave },
// No pisa lo ya editado por el admin: si existe, se deja como está
update: {},
create: {
clave,
nombre: extra?.nombre ?? nombreComercial(clave),
// `tipo` no se deduce del inventario actual (los códigos y el prefijo
// del PDF no lo distinguen): queda el default VERTICAL y el admin lo
// ajusta desde el módulo.
tipo: extra?.tipo ?? 'VERTICAL',
prefijoUnidadPdf: typeof parametros?.prefijoUnidadPdf === 'string' ? parametros.prefijoUnidadPdf : null,
},
})
if (previa) fichasExistentes++
else fichasNuevas++
for (const nombre of [...(modelosPorClave.get(clave) ?? [])].sort()) {
const modeloPrevio = await prisma.modeloDesarrollo.findUnique({
where: { desarrolloId_nombre: { desarrolloId: ficha.id, nombre } },
select: { id: true },
})
await prisma.modeloDesarrollo.upsert({
where: { desarrolloId_nombre: { desarrolloId: ficha.id, nombre } },
update: {},
create: { desarrolloId: ficha.id, nombre },
})
if (!modeloPrevio) modelosNuevos++
}
if (parametros) {
const reglaPrevia = await prisma.reglaCotizadorDesarrollo.findUnique({
where: { desarrolloId: ficha.id },
select: { desarrolloId: true },
})
if (!reglaPrevia) {
await prisma.reglaCotizadorDesarrollo.create({
data: { desarrolloId: ficha.id, parametrosJson: JSON.stringify(parametros) },
})
reglasNuevas++
}
}
}
const total = await prisma.desarrollo.count()
const conReglas = await prisma.reglaCotizadorDesarrollo.count()
const totalModelos = await prisma.modeloDesarrollo.count()
// eslint-disable-next-line no-console
console.log(
`[seed-desarrollos] fichas: ${fichasNuevas} nueva(s), ${fichasExistentes} ya existían; ` +
`${modelosNuevos} modelo(s) nuevo(s); ${reglasNuevas} regla(s) de cotizador copiada(s)`,
)
// eslint-disable-next-line no-console
console.log(`[seed-desarrollos] total en BD: ${total} desarrollos, ${totalModelos} modelos, ${conReglas} con reglas`)
for (const d of await prisma.desarrollo.findMany({
orderBy: { clave: 'asc' },
include: { modelos: { orderBy: { nombre: 'asc' } }, reglas: { select: { desarrolloId: true } } },
})) {
// eslint-disable-next-line no-console
console.log(
` ${d.clave} (${d.estado}/${d.tipo}) — ${d.modelos.length} modelo(s)` +
`${d.reglas ? '' : ' — SIN reglas de cotizador'}: ${d.modelos.map((m) => m.nombre).join(', ') || '—'}`,
)
}
}
main()
.catch((err) => {
console.error('[seed-desarrollos] error:', err)
process.exitCode = 1
})
.finally(() => prisma.$disconnect())