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).
This commit is contained in:
157
api-gateway/scripts/seed-desarrollos.js
Normal file
157
api-gateway/scripts/seed-desarrollos.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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())
|
||||
Reference in New Issue
Block a user