feat(cotizador): multi-desarrollo — ATELIER, ESTANZA, SENDA, SOLANA + DIAGONAL
- 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
This commit is contained in:
125
scripts/generar-seeds.py
Normal file
125
scripts/generar-seeds.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Genera apps/cotizador/src/data/unidades-{dev}.ts desde reference/cotizadores/{DEV}.json.
|
||||
|
||||
- Prefija el código con el desarrollo (ATELIER-10, ESTANZA-52, SENDA-411-1-7, SOLANA-6)
|
||||
para que sea único en todo el ecosistema (board Monday compartido).
|
||||
- En desarrollos de precio único, precioSinAcabados/precioConAcabados replican
|
||||
precioLista (el tipo los requiere numéricos; el flag precioUnico del desarrollo
|
||||
es el que manda en la UI).
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = '/root/frasa-ecosystem'
|
||||
|
||||
def num(v):
|
||||
return v if isinstance(v, (int, float)) else None
|
||||
|
||||
def ts_num(v):
|
||||
return 'null' if v is None else repr(float(v)).rstrip('0').rstrip('.') if isinstance(v, float) and v == int(v) else repr(v)
|
||||
|
||||
def ts_str(v):
|
||||
if v is None:
|
||||
return 'null'
|
||||
s = str(v).replace('\\', '\\\\').replace("'", "\\'")
|
||||
return f"'{s}'"
|
||||
|
||||
def slug(codigo):
|
||||
return re.sub(r'[^A-Za-z0-9]+', '_', str(codigo)).strip('_')
|
||||
|
||||
def extras_ts(pairs):
|
||||
items = ', '.join(f'{k}: {ts_str(v) if isinstance(v, str) or v is None else ts_num(v)}' for k, v in pairs)
|
||||
return '{ ' + items + ' }'
|
||||
|
||||
def gen(dev, codigo_fn, map_fn, nota):
|
||||
data = json.load(open(f'{ROOT}/reference/cotizadores/{dev}.json'))
|
||||
unidades = data['unidades']
|
||||
lines = [
|
||||
"import type { Unidad } from '../types'",
|
||||
'',
|
||||
f'// Inventario {dev} — {len(unidades)} unidades. {nota}',
|
||||
f'// Generado desde reference/cotizadores/{dev}.json (NO editar a mano; regenerar con scripts/generar-seeds.py).',
|
||||
f'// Precio único por unidad: precioSinAcabados/precioConAcabados replican precioLista.',
|
||||
f'export const UNIDADES_{dev}: Unidad[] = [',
|
||||
]
|
||||
for u in unidades:
|
||||
precio = map_fn('precio', u)
|
||||
campos = {
|
||||
'codigo': ts_str(codigo_fn(u)),
|
||||
'modelo': ts_str(u.get('modelo') or ''),
|
||||
'desarrollo': ts_str(dev),
|
||||
'fechaEntrega': ts_str(u.get('fechaEntrega')),
|
||||
'supEstacionamiento': ts_num(num(u.get('supEstacionamiento'))),
|
||||
'supConstruccion': ts_num(num(u.get('supConstruccion'))),
|
||||
'supExcedente': ts_num(num(u.get('supExcedente'))),
|
||||
'precioSinAcabados': ts_num(precio if precio is not None else 0),
|
||||
'precioConAcabados': ts_num(precio if precio is not None else 0),
|
||||
'precioLista': ts_num(precio),
|
||||
'status': ts_str(u.get('status')),
|
||||
'mesesParaFirma': ts_num(num(map_fn('mesesParaFirma', u))),
|
||||
'extras': extras_ts(map_fn('extras', u)),
|
||||
}
|
||||
body = ', '.join(f'{k}: {v}' for k, v in campos.items() if v is not None)
|
||||
lines.append(f' {{ {body} }},')
|
||||
lines.append(']')
|
||||
lines.append('')
|
||||
out = f'{ROOT}/apps/cotizador/src/data/unidades-{dev.lower()}.ts'
|
||||
open(out, 'w').write('\n'.join(lines))
|
||||
print(f'{out}: {len(unidades)} unidades')
|
||||
|
||||
def precio_de(u, *claves):
|
||||
ex = u.get('extras') or {}
|
||||
for c in claves:
|
||||
v = ex.get(c)
|
||||
if isinstance(v, (int, float)):
|
||||
return v
|
||||
return None
|
||||
|
||||
gen(
|
||||
'ATELIER',
|
||||
lambda u: f"ATELIER-{u['codigo']}",
|
||||
lambda k, u: {
|
||||
'precio': precio_de(u, 'precioLista'),
|
||||
'mesesParaFirma': None,
|
||||
'extras': [('prototipo', u['extras'].get('prototipo')), ('manzana', u['extras'].get('manzana')),
|
||||
('lote', u['extras'].get('lote')), ('cajones', u['extras'].get('cajones'))],
|
||||
}[k],
|
||||
'Departamentos, MZA 32 LOTE 1.',
|
||||
)
|
||||
|
||||
gen(
|
||||
'ESTANZA',
|
||||
lambda u: f"ESTANZA-{str(u['codigo']).replace('CASA-', '')}",
|
||||
lambda k, u: {
|
||||
'precio': precio_de(u, 'precioLista'),
|
||||
'mesesParaFirma': None,
|
||||
'extras': [('manzana', u['extras'].get('manzana')), ('lote', u['extras'].get('lote')),
|
||||
('cajones', u['extras'].get('cajones'))],
|
||||
}[k],
|
||||
'Casas DUPLO A/B, MZA 92 LOTE 32.',
|
||||
)
|
||||
|
||||
gen(
|
||||
'SENDA',
|
||||
lambda u: f"SENDA-{u['codigo']}",
|
||||
lambda k, u: {
|
||||
'precio': precio_de(u, 'precioPreventa'),
|
||||
'mesesParaFirma': None,
|
||||
'extras': [('prototipo', u['extras'].get('prototipo')), ('manzana', u['extras'].get('manzana')),
|
||||
('lote', u['extras'].get('lote')), ('cajones', u['extras'].get('cajones'))],
|
||||
}[k],
|
||||
'Casas; fechaEntrega null cuando la unidad no está en la tabla TERM (mensualidades no calculables).',
|
||||
)
|
||||
|
||||
gen(
|
||||
'SOLANA',
|
||||
lambda u: f"SOLANA-{u['codigo']}",
|
||||
lambda k, u: {
|
||||
'precio': precio_de(u, 'precioPreventa'),
|
||||
'mesesParaFirma': u['extras'].get('mesesParaFirma'),
|
||||
'extras': [('prototipo', u['extras'].get('prototipo')), ('etapa', u['extras'].get('etapa')),
|
||||
('cajones', u['extras'].get('cajones'))],
|
||||
}[k],
|
||||
'Casas etapas SOLANA I/II; mesesParaFirma (1-4) es el plazo de mensualidades.',
|
||||
)
|
||||
Reference in New Issue
Block a user