- 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
126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
#!/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.',
|
|
)
|