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:
@@ -27,7 +27,7 @@ npm test # vitest: fórmulas del cotizador (fixture U-101), cobranza, monday.s
|
||||
| Método | Ruta | Descripción |
|
||||
|---|---|---|
|
||||
| GET | `/api/health` | `{status, monday: 'up'|'down'}` |
|
||||
| GET | `/api/unidades` | Inventario desde Monday (`?disponibles=true` filtra) |
|
||||
| GET | `/api/unidades` | Inventario desde Monday (`?disponibles=true` filtra; `?desarrollo=` filtra por DIAGONAL/ATELIER/ESTANZA/SENDA/SOLANA) |
|
||||
| GET | `/api/unidades/:codigo` | Una unidad (404 si no existe) |
|
||||
| POST | `/api/cotizaciones` | Calcula propuesta (server-side), persiste y crea Oportunidad en Monday |
|
||||
| GET | `/api/cotizaciones` · `/api/cotizaciones/:id` | Consulta |
|
||||
|
||||
23
api-gateway/knowledge/catalogo-atelier.md
Normal file
23
api-gateway/knowledge/catalogo-atelier.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Catálogo de unidades — Desarrollo ATELIER
|
||||
|
||||
ATELIER es un desarrollo de **departamentos** de Frasa Desarrollos (manzana 32, lote 1). Inventario: 34 departamentos; al 21-ago-2026 solo **4 están DISPONIBLES** (deptos 10, 17, 27 y 34), 1 vendido y 29 escriturados. Entrega inmediata: la obra ya terminó, por lo que las mensualidades son **un solo pago** en todas las propuestas.
|
||||
|
||||
## Modelos
|
||||
|
||||
- **SOHO** (prototipos E-A / E-B) — ~52–53 m² habitables. Precios de lista entre $2,059,713 y $2,666,831 MXN.
|
||||
- **TRIBECA** (prototipos 2R-A / 2R-B / 2R-C) — ~75–83 m² habitables. Precios entre $2,985,326 y $4,451,040 MXN. Los 4 departamentos disponibles son TRIBECA ($3,750,500 a $4,250,600).
|
||||
|
||||
ATELIER tiene **precio único** por departamento: no existe distinción con/sin acabados (cocina y clóset). El apartado estándar es $20,000 MXN y se descuenta del enganche. Cuota de mantenimiento: pendiente de definir.
|
||||
|
||||
## Esquema de propuestas
|
||||
|
||||
Las 4 propuestas de ATELIER son **CONTADO, SEMI CONTADO, COFINAVIT y BANCARIO** (no hay Infonavit). Descuentos por defecto: 0% en las cuatro (editables en el cotizador). N = 1 pago siempre.
|
||||
|
||||
- **Contado** — enganche 90% del precio final menos apartado; pago final 10%. Sin mensualidades.
|
||||
- **Semi contado** — enganche 50% del precio final menos apartado; mensualidades 40% en 1 pago; pago final 10%.
|
||||
- **Cofinavit** — enganche 2.5% del precio final menos apartado; mensualidades 2.5% en 1 pago; pago final 95%. (Es la estructura del Infonavit de DIAGONAL pero calculada sobre precio final, sin el quirk del precio de lista.)
|
||||
- **Bancario** — enganche 5% del **precio de lista** menos apartado; mensualidades = remanente (precio final − pago final − enganche − apartado) en 1 pago; pago final 90% del precio final.
|
||||
|
||||
Ejemplo validado (depto 10, precio $3,997,500, apartado $20,000): Contado enganche $3,577,750 / pago final $399,750; Semi enganche $1,978,750 / mensualidad $1,599,000 / pago final $399,750; Cofinavit enganche $79,937.50 / mensualidad $99,937.50 / pago final $3,797,625; Bancario enganche $179,875 / mensualidad $199,875 / pago final $3,597,750.
|
||||
|
||||
Vigencia de la cotización: 3 días naturales; 7 días para formalizar tras el apartado. No incluye gastos de escrituración.
|
||||
23
api-gateway/knowledge/catalogo-estanza.md
Normal file
23
api-gateway/knowledge/catalogo-estanza.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Catálogo de unidades — Desarrollo ESTANZA
|
||||
|
||||
ESTANZA es un desarrollo de **casas** de Frasa Desarrollos (manzana 92, lote 32). Inventario: 64 casas; al 21-ago-2026 hay **13 DISPONIBLES** (9 DUPLO A y 4 DUPLO B), 2 apartadas, 5 vendidas y 44 escrituradas.
|
||||
|
||||
## Modelos
|
||||
|
||||
- **DUPLO A** — 69.06 m² de construcción (63.99 m² habitables), terreno tipo 56.17 m², 1 cajón. Precios entre $2,248,242 y $2,752,486 MXN.
|
||||
- **DUPLO B** — 69.18 m² de construcción (64.05 m² habitables), terreno tipo 63.83 m², 1 cajón. Precios entre $2,241,564 y $2,653,052 MXN.
|
||||
|
||||
Las casas disponibles van de $2,345,652 a $2,581,017 MXN. ESTANZA tiene **precio único** por casa (sin distinción de acabados). Apartado estándar $20,000 MXN (se descuenta del enganche). Cuota de mantenimiento: pendiente de definir. 7 casas marcadas "R.U.V." aplican a Infonavit tradicional.
|
||||
|
||||
## Esquema de propuestas
|
||||
|
||||
Las mismas **4 propuestas que DIAGONAL** (Bancario, Infonavit-Cofinavit, Contado, Semi contado), con descuentos por defecto 0% en las cuatro (editables). El plazo N es fijo: **1 mensualidad** (terminación de obra = 1 mes en todo el inventario).
|
||||
|
||||
- **Bancario** — enganche 2.5% del precio final menos apartado; mensualidades 7.5% del precio final ÷ N; **pago final 90% del precio de LISTA** (quirk heredado: con descuento > 0 la suma no cuadra, así está en el Excel).
|
||||
- **Infonavit-Cofinavit** — enganche 2.5% del precio final menos apartado; mensualidades 2.5% del precio final ÷ N; **pago final 95% del precio de LISTA** (mismo quirk).
|
||||
- **Contado** — enganche 90% del precio final menos apartado; pago final 10%. Sin mensualidades.
|
||||
- **Semi contado** — enganche 50% del precio final menos apartado; mensualidades 40% del precio final ÷ N; pago final 10%.
|
||||
|
||||
Ejemplo validado (casa 52, precio $2,437,250, apartado $20,000, N=1): Bancario enganche $40,931.25 / mensualidad $182,793.75 / pago final $2,193,525; Infonavit enganche $40,931.25 / mensualidad $60,931.25 / pago final $2,315,387.50; Contado enganche $2,173,525 / pago final $243,725; Semi contado enganche $1,198,625 / mensualidad $974,900 / pago final $243,725.
|
||||
|
||||
Vigencia de la cotización: 3 días naturales; 7 días para formalizar. No incluye gastos de escrituración.
|
||||
27
api-gateway/knowledge/catalogo-senda.md
Normal file
27
api-gateway/knowledge/catalogo-senda.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Catálogo de unidades — Desarrollo SENDA
|
||||
|
||||
SENDA es un desarrollo de **casas** de Frasa Desarrollos. Los códigos de unidad tienen la forma `manzana-lote-casa` (ej. 411-1-7 = manzana 411, lote 1, casa 7). Inventario: **139 casas**; al 19-ago-2026 hay **43 DISPONIBLES**, 1 apartada, 56 vendidas y 39 bloqueadas.
|
||||
|
||||
## Modelos
|
||||
|
||||
- **HIMALAYA** (prototipo UNE-C) — 69.33 m² de construcción. Precios $2,287,000 – $2,468,483 MXN. 14 disponibles.
|
||||
- **SANTIAGO** (prototipo LAUREL) — 63.95 m². Precios $2,450,000 – $2,750,000 MXN. 6 disponibles.
|
||||
- **ESMERALDA PB / PA** (prototipo CUADRUPLEX) — 55.2 / 55.78 m². Precio único $1,948,000 (PB) y $1,850,000 (PA). 23 disponibles.
|
||||
- **SANTA CRUZ** (prototipo DUPLO) — 2 unidades, ambas vendidas.
|
||||
|
||||
Las casas disponibles van de $1,850,000 a $2,700,000 MXN. SENDA tiene **precio único** (precio preventa; sin distinción de acabados). Apartado estándar $20,000 MXN, que se descuenta del enganche. Cuota de mantenimiento: pendiente de definir.
|
||||
|
||||
## Esquema de propuestas
|
||||
|
||||
Las 4 propuestas (Bancario, Infonavit-Cofinavit, Contado, Semi contado) calculan **todo sobre el precio final** (sin el quirk del precio de lista de DIAGONAL):
|
||||
|
||||
- **Bancario** — enganche **5%** del precio final menos apartado; mensualidades 5% del precio final ÷ N; pago final 90%. Descuento default 0% (editable).
|
||||
- **Infonavit-Cofinavit** — enganche 2.5% del precio final menos apartado; mensualidades 2.5% ÷ N; pago final 95%. Descuento default 0% (editable).
|
||||
- **Contado** — descuento **2% fijo**; enganche 90% del precio final menos apartado; pago final 10%. Sin mensualidades.
|
||||
- **Semi contado** — descuento **1% fijo**; esquema **50 / 45 / 5**: enganche 50% del precio final menos apartado, mensualidades 45% ÷ N, pago final **5%** (en DIAGONAL es 40% y 10%).
|
||||
|
||||
N = meses aproximados a la escritura, calculado como (fecha de entrega − hoy) / 30.4 días. **Ojo:** las unidades que aún no tienen fecha de terminación de obra (todas las CUADRUPLEX 411-25-* y varias más) no permiten calcular mensualidades; el cotizador muestra "—" en ese concepto, pero enganche y pago final sí se calculan.
|
||||
|
||||
Ejemplo validado (casa 411-1-7, precio $2,451,750, N=4, sin apartado registrado): Bancario enganche $122,587.50 / mensualidad $30,646.88 / pago final $2,206,575; Infonavit enganche $61,293.75 / mensualidad $15,323.44 / pago final $2,329,162.50; Contado (2% desc) precio final $2,402,715, enganche $2,162,443.50, pago final $240,271.50; Semi (1% desc) precio final $2,427,232.50, enganche $1,213,616.25, mensualidad $273,063.66, pago final $121,361.63.
|
||||
|
||||
Vigencia de la cotización: 3 días naturales; 7 días para formalizar tras el apartado. No incluye gastos de escrituración.
|
||||
25
api-gateway/knowledge/catalogo-solana.md
Normal file
25
api-gateway/knowledge/catalogo-solana.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Catálogo de unidades — Desarrollo SOLANA
|
||||
|
||||
SOLANA es un desarrollo de **casas** de Frasa Desarrollos en dos etapas: SOLANA I (ubicaciones 1A–5A) y SOLANA II (ubicaciones 6–10). Inventario: **10 casas**; al 21-ago-2026 quedan **3 DISPONIBLES** (casas 6, 7 y 9, todas MIRADOR de etapa SOLANA II), 3 vendidas y 4 escrituradas.
|
||||
|
||||
## Modelos
|
||||
|
||||
- **PANORAMA** (prototipo UNE-C) — 69.33 m² de construcción, 2 cajones. Precios $2,767,518 – $2,990,445 MXN (etapa SOLANA I; sin disponibles).
|
||||
- **MIRADOR** (prototipos LAUREL, DUPLO-A, DUPLO-B) — 56.17 – 69.33 m². Precios $2,560,000 – $3,400,000 MXN. Las 3 disponibles ($2,560,000 a $2,605,000) son MIRADOR.
|
||||
|
||||
SOLANA tiene **precio único** (precio preventa; sin distinción de acabados). Apartado estándar $20,000 MXN, que se descuenta del enganche. Cuota de mantenimiento: pendiente.
|
||||
|
||||
## Esquema de propuestas
|
||||
|
||||
Las 4 propuestas (Bancario, Infonavit, Contado, Semi contado) con descuentos por defecto 0% (editables; cada propuesta admite dos conceptos de descuento que se suman). Todo se calcula sobre el **precio final** (sin el quirk del precio de lista de DIAGONAL):
|
||||
|
||||
- **Bancario** — enganche **5%** del precio final menos apartado; mensualidades 5% del precio final ÷ N; pago final 90%.
|
||||
- **Infonavit** — **enganche 0%** (no se descuenta apartado porque no hay enganche); mensualidades 5% del precio final ÷ N; pago final 95%.
|
||||
- **Contado** — enganche 90% del precio final menos apartado; pago final 10%. Sin mensualidades.
|
||||
- **Semi contado** — enganche 50% del precio final menos apartado; mensualidades 40% ÷ N; pago final 10%.
|
||||
|
||||
N no viene de una fecha de entrega: es el dato **"meses para firma aprox"** del inventario, propio de cada casa (1 a 4 meses).
|
||||
|
||||
Ejemplo validado (casa 6, precio $2,560,000, N=1, apartado $20,000): Bancario enganche $108,000 / mensualidad $128,000 / pago final $2,304,000; Infonavit enganche $0 / mensualidad $128,000 / pago final $2,432,000; Contado enganche $2,284,000 / pago final $256,000; Semi contado enganche $1,260,000 / mensualidad $1,024,000 / pago final $256,000.
|
||||
|
||||
Vigencia de la cotización: 3 días naturales; 7 días para formalizar tras el apartado. No incluye gastos de escrituración.
|
||||
@@ -1,15 +1,22 @@
|
||||
# Las 4 propuestas del cotizador Frasa
|
||||
|
||||
El cotizador genera siempre 4 propuestas comerciales para la misma unidad. Todas parten del **precio de lista** (sin o con acabados, según lo que elija el cliente) y del **apartado** (por defecto $20,000 MXN), que ya se pagó y se descuenta del enganche.
|
||||
El cotizador genera 4 propuestas comerciales para la misma unidad. Todas parten del **precio de lista** (sin o con acabados, según lo que elija el cliente; en ATELIER, ESTANZA, SENDA y SOLANA hay un solo precio) y del **apartado** (por defecto $20,000 MXN), que ya se pagó y se descuenta del enganche.
|
||||
|
||||
**Este documento describe el esquema de DIAGONAL.** Los demás desarrollos usan la misma estructura pero con porcentajes y reglas propios; ver el catálogo de cada desarrollo:
|
||||
|
||||
- **ATELIER** — Contado / Semi contado / **Cofinavit** (no hay Infonavit) / Bancario. Bancario con enganche 5% sobre precio de lista. Entrega inmediata: las mensualidades son 1 solo pago. Descuentos default 0%.
|
||||
- **ESTANZA** — las mismas 4 de DIAGONAL, pero el enganche se calcula sobre el precio final y el **pago final de Bancario/Infonavit sobre el precio de lista**; N fijo = 1 mensualidad. Descuentos default 0%.
|
||||
- **SENDA** — Bancario con enganche 5%; Semi contado 50/45/**5**; Contado con 2% y Semi con 1% de descuento default; todo sobre precio final (sin quirk de lista). Unidades sin fecha de entrega: no se pueden calcular mensualidades ("—").
|
||||
- **SOLANA** — Bancario con enganche 5%; **Infonavit con enganche 0%**; N = "meses para firma" de cada casa (1–4). Descuentos default 0%.
|
||||
|
||||
Elementos comunes a las 4 propuestas:
|
||||
|
||||
- **Descuento**: fracción sobre el precio de lista. Valores por defecto: Bancario 0%, Infonavit-Cofinavit 0%, Contado 2%, Semi-contado 1%.
|
||||
- **Descuento**: fracción sobre el precio de lista. Valores por defecto en DIAGONAL: Bancario 0%, Infonavit-Cofinavit 0%, Contado 2%, Semi-contado 1%.
|
||||
- **Monto de descuento** = precio de lista × descuento.
|
||||
- **Precio final** = precio de lista − monto de descuento.
|
||||
- **Número de mensualidades (N)**: meses completos desde hoy hasta la fecha de entrega de la unidad (mínimo 1). Las mensualidades se pagan durante la construcción.
|
||||
|
||||
## Propuesta 1 — Crédito Bancario
|
||||
## Propuesta 1 — Crédito Bancario (DIAGONAL)
|
||||
|
||||
Para clientes que liquidarán con crédito hipotecario bancario a la entrega.
|
||||
|
||||
@@ -17,7 +24,7 @@ Para clientes que liquidarán con crédito hipotecario bancario a la entrega.
|
||||
- Pago final (contra entrega, cubierto por el crédito) = 90% del precio final con descuento.
|
||||
- Mensualidades = precio final − apartado − enganche − pago final, dividido entre N.
|
||||
|
||||
## Propuesta 2 — Crédito Infonavit / Cofinavit
|
||||
## Propuesta 2 — Crédito Infonavit / Cofinavit (DIAGONAL)
|
||||
|
||||
Para clientes que liquidarán con crédito Infonavit o Cofinavit (Infonavit + banco) a la entrega.
|
||||
|
||||
@@ -25,7 +32,7 @@ Para clientes que liquidarán con crédito Infonavit o Cofinavit (Infonavit + ba
|
||||
- Pago final (contra entrega, cubierto por el crédito) = 95% del precio final con descuento.
|
||||
- Mensualidades = precio de **lista** − pago final − apartado − enganche, dividido entre N. (Ojo: el Excel del cotizador usa aquí el precio de lista, no el precio final; así está validado y así se calcula.)
|
||||
|
||||
## Propuesta 3 — Contado
|
||||
## Propuesta 3 — Contado (DIAGONAL)
|
||||
|
||||
Sin mensualidades: el cliente paga casi todo de inmediato.
|
||||
|
||||
@@ -33,7 +40,7 @@ Sin mensualidades: el cliente paga casi todo de inmediato.
|
||||
- Pago final = 10% del precio final con descuento.
|
||||
- Mensualidad: no aplica (N/A).
|
||||
|
||||
## Propuesta 4 — Semi-contado
|
||||
## Propuesta 4 — Semi-contado (DIAGONAL)
|
||||
|
||||
Esquema 50 / 40 / 10 sobre el precio final con descuento.
|
||||
|
||||
|
||||
189
api-gateway/scripts/importar-unidades.ts
Normal file
189
api-gateway/scripts/importar-unidades.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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<void> => 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<string, string | number | null>
|
||||
}
|
||||
|
||||
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<Map<string, { id: string; title: string; type: string }>> {
|
||||
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<void> {
|
||||
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<Set<string>> {
|
||||
const nombres = new Set<string>()
|
||||
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, { id: string; title: string; type: string }>,
|
||||
): string {
|
||||
const valores: Record<string, unknown> = {}
|
||||
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<void> {
|
||||
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)
|
||||
})
|
||||
@@ -12,7 +12,7 @@ const crearCotizacionSchema = z.object({
|
||||
clienteNombre: z.string().min(1),
|
||||
clienteEmail: z.string().email().optional(),
|
||||
clienteTelefono: z.string().min(7).optional(),
|
||||
tipoPropuesta: z.enum(['BANCARIO', 'INFONAVIT', 'CONTADO', 'SEMI_CONTADO']),
|
||||
tipoPropuesta: z.enum(['BANCARIO', 'INFONAVIT', 'COFINAVIT', 'CONTADO', 'SEMI_CONTADO']),
|
||||
descuento: z.number().min(0).max(1).optional(), // fracción (0.02 = 2%)
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function crearCotizacion(req: Request, res: Response): Promise<void
|
||||
precioLista,
|
||||
precioFinal: propuesta.precioFinal,
|
||||
enganche: propuesta.enganche,
|
||||
numMensualidades: propuesta.numMensualidades,
|
||||
numMensualidades: propuesta.numMensualidades ?? 0, // 0 => plazo desconocido (sin fecha de entrega)
|
||||
mensualidad: propuesta.mensualidad,
|
||||
pagoFinal: propuesta.pagoFinal,
|
||||
},
|
||||
|
||||
@@ -2,11 +2,16 @@ import type { Request, Response } from 'express'
|
||||
import { mondayService } from '../services/monday.service.js'
|
||||
import { NotFoundError } from '../utils/errors.js'
|
||||
|
||||
/** GET /api/unidades — ?disponibles=true filtra solo DISPONIBLE */
|
||||
/** GET /api/unidades — ?disponibles=true filtra solo DISPONIBLE; ?desarrollo= filtra por desarrollo */
|
||||
export async function listarUnidades(req: Request, res: Response): Promise<void> {
|
||||
const unidades = await mondayService.getUnidades()
|
||||
const disponibles = req.query['disponibles'] === 'true'
|
||||
res.json(disponibles ? unidades.filter((u) => u.status === 'DISPONIBLE') : unidades)
|
||||
const desarrollo = req.query['desarrollo']?.toString().toUpperCase()
|
||||
let lista = disponibles ? unidades.filter((u) => u.status === 'DISPONIBLE') : unidades
|
||||
if (desarrollo) {
|
||||
lista = lista.filter((u) => (u.desarrollo || 'DIAGONAL').toUpperCase() === desarrollo)
|
||||
}
|
||||
res.json(lista)
|
||||
}
|
||||
|
||||
/** GET /api/unidades/:codigo — 404 si no existe */
|
||||
|
||||
166
api-gateway/src/services/cotizacion-desarrollos.service.test.ts
Normal file
166
api-gateway/src/services/cotizacion-desarrollos.service.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
APARTADO_DEFAULT,
|
||||
calcularPropuesta,
|
||||
calcularPropuestas,
|
||||
totalPagado,
|
||||
type UnidadCotizable,
|
||||
} from './cotizacion.service.js'
|
||||
|
||||
/**
|
||||
* Fixtures de los 4 desarrollos nuevos, validados al centavo contra los Excel
|
||||
* de producción (reference/cotizadores/*.md). Espejo de
|
||||
* apps/cotizador/src/lib/calculations-desarrollos.test.ts.
|
||||
*/
|
||||
|
||||
function unidad(parcial: Partial<UnidadCotizable> & Pick<UnidadCotizable, 'codigo' | 'desarrollo'>): UnidadCotizable {
|
||||
return {
|
||||
modelo: 'X',
|
||||
fechaEntrega: null,
|
||||
supEstacionamiento: 0,
|
||||
supConstruccion: 0,
|
||||
supExcedente: 0,
|
||||
precioSinAcabados: 0,
|
||||
precioConAcabados: 0,
|
||||
status: 'DISPONIBLE',
|
||||
...parcial,
|
||||
}
|
||||
}
|
||||
|
||||
const DESC_CERO = { creditoBancario: 0, creditoInfonavit: 0, contado: 0, semiContado: 0 }
|
||||
|
||||
describe('ATELIER — depto 10 (L = 3,997,500, apartado 20,000, N fijo = 1)', () => {
|
||||
const depto10 = unidad({
|
||||
codigo: 'ATELIER-10',
|
||||
desarrollo: 'ATELIER',
|
||||
fechaEntrega: '2026-01-21',
|
||||
precioSinAcabados: 3997500,
|
||||
precioConAcabados: 3997500,
|
||||
})
|
||||
|
||||
it('BANCARIO: enganche 5% sobre LISTA, mensualidad remanente en 1 pago', () => {
|
||||
const [p] = calcularPropuestas({
|
||||
unidad: depto10,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: DESC_CERO,
|
||||
})
|
||||
expect(p?.enganche).toBeCloseTo(179875, 2)
|
||||
expect(p?.mensualidadesTotal).toBeCloseTo(199875, 2)
|
||||
expect(p?.pagoFinal).toBeCloseTo(3597750, 2)
|
||||
})
|
||||
|
||||
it('COFINAVIT (vía tipo COFINAVIT del API): 2.5% / 95% sobre precio final', () => {
|
||||
const p = calcularPropuesta(depto10, 'COFINAVIT', false, APARTADO_DEFAULT)
|
||||
expect(p.titulo).toBe('Crédito Cofinavit')
|
||||
expect(p.enganche).toBeCloseTo(79937.5, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(99937.5, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(3797625, 2)
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(3997500, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ESTANZA — casa 52 (L = 2,437,250): pago final sobre LISTA (quirk)', () => {
|
||||
const casa52 = unidad({
|
||||
codigo: 'ESTANZA-52',
|
||||
desarrollo: 'ESTANZA',
|
||||
precioSinAcabados: 2437250,
|
||||
precioConAcabados: 2437250,
|
||||
})
|
||||
|
||||
it('las 4 propuestas al centavo', () => {
|
||||
const [bancario, infonavit, contado, semi] = calcularPropuestas({
|
||||
unidad: casa52,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: DESC_CERO,
|
||||
})
|
||||
expect(bancario?.enganche).toBeCloseTo(40931.25, 2)
|
||||
expect(bancario?.mensualidadesTotal).toBeCloseTo(182793.75, 2)
|
||||
expect(bancario?.pagoFinal).toBeCloseTo(2193525, 2)
|
||||
expect(infonavit?.enganche).toBeCloseTo(40931.25, 2)
|
||||
expect(infonavit?.mensualidadesTotal).toBeCloseTo(60931.25, 2)
|
||||
expect(infonavit?.pagoFinal).toBeCloseTo(2315387.5, 2)
|
||||
expect(contado?.enganche).toBeCloseTo(2173525, 2)
|
||||
expect(contado?.pagoFinal).toBeCloseTo(243725, 2)
|
||||
expect(semi?.enganche).toBeCloseTo(1198625, 2)
|
||||
expect(semi?.mensualidadesTotal).toBeCloseTo(974900, 2)
|
||||
expect(semi?.pagoFinal).toBeCloseTo(243725, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SENDA — 411-1-7 (L = 2,451,750, apartado 0, N = 4)', () => {
|
||||
const casa = unidad({
|
||||
codigo: 'SENDA-411-1-7',
|
||||
desarrollo: 'SENDA',
|
||||
fechaEntrega: '2026-12-19', // la N real se fuerza con numMeses en el test
|
||||
precioSinAcabados: 2451750,
|
||||
precioConAcabados: 2451750,
|
||||
})
|
||||
|
||||
it('semi-contado 50/45/5 con desc 1% default', () => {
|
||||
const p = calcularPropuesta(casa, 'SEMI_CONTADO', false, 0)
|
||||
// N real depende de hoy; validamos montos que no dependen de N
|
||||
expect(p.precioFinal).toBeCloseTo(2427232.5, 2)
|
||||
expect(p.enganche).toBeCloseTo(1213616.25, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(1092254.625, 3)
|
||||
expect(p.pagoFinal).toBeCloseTo(121361.625, 3)
|
||||
})
|
||||
|
||||
it('bancario 5/5/90 con N forzada = 4', () => {
|
||||
const [p] = calcularPropuestas({
|
||||
unidad: casa,
|
||||
acabados: false,
|
||||
apartado: 0,
|
||||
descuentos: DESC_CERO,
|
||||
numMeses: 4,
|
||||
})
|
||||
expect(p?.enganche).toBeCloseTo(122587.5, 2)
|
||||
expect(p?.mensualidad).toBeCloseTo(30646.875, 3)
|
||||
expect(p?.pagoFinal).toBeCloseTo(2206575, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SENDA — unidad sin fecha de entrega', () => {
|
||||
it('N = null, mensualidades "—" y total pagado null', () => {
|
||||
const sinFecha = unidad({
|
||||
codigo: 'SENDA-411-25-A101',
|
||||
desarrollo: 'SENDA',
|
||||
fechaEntrega: null,
|
||||
precioSinAcabados: 1948000,
|
||||
precioConAcabados: 1948000,
|
||||
})
|
||||
const p = calcularPropuesta(sinFecha, 'BANCARIO', false, APARTADO_DEFAULT)
|
||||
expect(p.numMensualidades).toBeNull()
|
||||
expect(p.mensualidad).toBeNull()
|
||||
expect(p.notaMensualidades).toBe('sin fecha de entrega')
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeNull()
|
||||
expect(p.enganche).toBeCloseTo(0.05 * 1948000 - APARTADO_DEFAULT, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(0.9 * 1948000, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SOLANA — casa 6 (L = 2,560,000, mesesParaFirma = 1)', () => {
|
||||
const casa6 = unidad({
|
||||
codigo: 'SOLANA-6',
|
||||
desarrollo: 'SOLANA',
|
||||
precioSinAcabados: 2560000,
|
||||
precioConAcabados: 2560000,
|
||||
mesesParaFirma: 1,
|
||||
})
|
||||
|
||||
it('INFONAVIT con enganche 0% (guarda: no resta el apartado)', () => {
|
||||
const p = calcularPropuesta(casa6, 'INFONAVIT', false, APARTADO_DEFAULT)
|
||||
expect(p.enganche).toBe(0)
|
||||
expect(p.numMensualidades).toBe(1)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(128000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2432000, 2)
|
||||
})
|
||||
|
||||
it('BANCARIO 5% sobre final', () => {
|
||||
const p = calcularPropuesta(casa6, 'BANCARIO', false, APARTADO_DEFAULT)
|
||||
expect(p.enganche).toBeCloseTo(108000, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(128000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2304000, 2)
|
||||
})
|
||||
})
|
||||
@@ -21,7 +21,7 @@ const U101: UnidadCotizable = {
|
||||
precioSinAcabados: 4415000,
|
||||
precioConAcabados: 4579000,
|
||||
status: 'DISPONIBLE',
|
||||
desarrollo: 'SENDA',
|
||||
desarrollo: 'DIAGONAL',
|
||||
}
|
||||
const N = 14
|
||||
|
||||
@@ -130,7 +130,7 @@ describe('totalPagado', () => {
|
||||
describe('indiceMenorTotal', () => {
|
||||
it('contado (2% desc) tiene el menor total pagado', () => {
|
||||
const propuestas = calcular()
|
||||
const totales = propuestas.map((p) => totalPagado(p, APARTADO_DEFAULT))
|
||||
const totales = propuestas.map((p) => totalPagado(p, APARTADO_DEFAULT) ?? 0)
|
||||
expect(indiceMenorTotal(totales)).toBe(2)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
/**
|
||||
* Cálculos replicados fielmente del Excel de producción "COTIZADOR" de
|
||||
* Frasa Desarrollos (port de apps/cotizador/src/lib/calculations.ts).
|
||||
* NO "corregir" las fórmulas: algunas usan precio de lista donde
|
||||
* intuitivamente iría precio final; así está en el Excel y así debe quedar.
|
||||
* Cálculos replicados fielmente de los Excel de producción "COTIZADOR" de
|
||||
* Frasa Desarrollos (port parametrizado de apps/cotizador/src/lib/calculations.ts
|
||||
* + src/data/desarrollos.ts). NO "corregir" las fórmulas: algunas usan precio
|
||||
* de lista donde intuitivamente iría precio final; así está en los Excel y
|
||||
* así debe quedar.
|
||||
*/
|
||||
|
||||
export interface UnidadCotizable {
|
||||
codigo: string
|
||||
modelo: string
|
||||
fechaEntrega: string // ISO 'YYYY-MM-DD' (terminación de obra)
|
||||
fechaEntrega: string | null // ISO 'YYYY-MM-DD'; null si no aplica o se desconoce
|
||||
supEstacionamiento: number
|
||||
supConstruccion: number
|
||||
supExcedente: number
|
||||
precioSinAcabados: number
|
||||
precioConAcabados: number
|
||||
status: 'DISPONIBLE' | 'APARTADA' | 'VENDIDA'
|
||||
status: string // DISPONIBLE | APARTADA | VENDIDA | APARTADO | VENDIDO | ESCRITURADO | BLOQUEADO
|
||||
desarrollo: string
|
||||
/** Plazo de mensualidades cuando lo fija el inventario (SOLANA: meses para firma) */
|
||||
mesesParaFirma?: number | null
|
||||
}
|
||||
|
||||
export type TipoPropuesta =
|
||||
@@ -33,7 +36,7 @@ export interface DescuentosPropuestas {
|
||||
|
||||
export const APARTADO_DEFAULT = 20000
|
||||
|
||||
// Descuentos por defecto de cada propuesta (fracción, no porcentaje)
|
||||
// Descuentos por defecto de DIAGONAL (fracción, no porcentaje)
|
||||
export const DESCUENTOS_DEFAULT: DescuentosPropuestas = {
|
||||
creditoBancario: 0,
|
||||
creditoInfonavit: 0,
|
||||
@@ -41,24 +44,123 @@ export const DESCUENTOS_DEFAULT: DescuentosPropuestas = {
|
||||
semiContado: 0.01,
|
||||
}
|
||||
|
||||
// ---------- Parametrización por desarrollo ----------
|
||||
|
||||
type BasePrecio = 'lista' | 'final'
|
||||
type ModoMensualidades = 'ninguna' | 'remanente' | 'remanenteLista' | 'porcentaje'
|
||||
|
||||
interface PropuestaConfig {
|
||||
tipo: TipoPropuesta
|
||||
claveDescuento: keyof DescuentosPropuestas
|
||||
titulo: string
|
||||
descuentoDefault: number
|
||||
pctEnganche: number
|
||||
engancheBase: BasePrecio
|
||||
pctPagoFinal: number
|
||||
pagoFinalBase: BasePrecio
|
||||
modoMensualidades: ModoMensualidades
|
||||
engancheCeroSinApartado?: boolean
|
||||
}
|
||||
|
||||
interface ParametrosDesarrollo {
|
||||
id: string
|
||||
precioUnico: boolean
|
||||
fuenteDeN: 'fechaEntrega' | 'fijo' | 'porUnidad'
|
||||
nFijo?: number
|
||||
propuestas: PropuestaConfig[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Espejo de apps/cotizador/src/data/desarrollos.ts (mantener sincronizados).
|
||||
* Detalle celda por celda en reference/cotizadores/*.md.
|
||||
*/
|
||||
export const DESARROLLOS: Record<string, ParametrosDesarrollo> = {
|
||||
DIAGONAL: {
|
||||
id: 'DIAGONAL',
|
||||
precioUnico: false,
|
||||
fuenteDeN: 'fechaEntrega',
|
||||
propuestas: [
|
||||
{ tipo: 'CREDITO_BANCARIO', claveDescuento: 'creditoBancario', titulo: 'Crédito Bancario', descuentoDefault: 0, pctEnganche: 0.025, engancheBase: 'lista', pctPagoFinal: 0.9, pagoFinalBase: 'final', modoMensualidades: 'remanente' },
|
||||
{ tipo: 'CREDITO_INFONAVIT_COFINAVIT', claveDescuento: 'creditoInfonavit', titulo: 'Crédito Infonavit-Cofinavit', descuentoDefault: 0, pctEnganche: 0.025, engancheBase: 'lista', pctPagoFinal: 0.95, pagoFinalBase: 'final', modoMensualidades: 'remanenteLista' },
|
||||
{ tipo: 'CONTADO', claveDescuento: 'contado', titulo: 'Contado', descuentoDefault: 0.02, pctEnganche: 0.9, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'ninguna' },
|
||||
{ tipo: 'SEMI_CONTADO', claveDescuento: 'semiContado', titulo: 'Semi Contado', descuentoDefault: 0.01, pctEnganche: 0.5, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
],
|
||||
},
|
||||
ATELIER: {
|
||||
id: 'ATELIER',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'fijo',
|
||||
nFijo: 1,
|
||||
propuestas: [
|
||||
{ tipo: 'CREDITO_BANCARIO', claveDescuento: 'creditoBancario', titulo: 'Crédito Bancario', descuentoDefault: 0, pctEnganche: 0.05, engancheBase: 'lista', pctPagoFinal: 0.9, pagoFinalBase: 'final', modoMensualidades: 'remanente' },
|
||||
{ tipo: 'CREDITO_INFONAVIT_COFINAVIT', claveDescuento: 'creditoInfonavit', titulo: 'Crédito Cofinavit', descuentoDefault: 0, pctEnganche: 0.025, engancheBase: 'final', pctPagoFinal: 0.95, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
{ tipo: 'CONTADO', claveDescuento: 'contado', titulo: 'Contado', descuentoDefault: 0, pctEnganche: 0.9, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'ninguna' },
|
||||
{ tipo: 'SEMI_CONTADO', claveDescuento: 'semiContado', titulo: 'Semi Contado', descuentoDefault: 0, pctEnganche: 0.5, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
],
|
||||
},
|
||||
ESTANZA: {
|
||||
id: 'ESTANZA',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'fijo',
|
||||
nFijo: 1,
|
||||
propuestas: [
|
||||
{ tipo: 'CREDITO_BANCARIO', claveDescuento: 'creditoBancario', titulo: 'Crédito Bancario', descuentoDefault: 0, pctEnganche: 0.025, engancheBase: 'final', pctPagoFinal: 0.9, pagoFinalBase: 'lista', modoMensualidades: 'porcentaje' },
|
||||
{ tipo: 'CREDITO_INFONAVIT_COFINAVIT', claveDescuento: 'creditoInfonavit', titulo: 'Crédito Infonavit-Cofinavit', descuentoDefault: 0, pctEnganche: 0.025, engancheBase: 'final', pctPagoFinal: 0.95, pagoFinalBase: 'lista', modoMensualidades: 'porcentaje' },
|
||||
{ tipo: 'CONTADO', claveDescuento: 'contado', titulo: 'Contado', descuentoDefault: 0, pctEnganche: 0.9, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'ninguna' },
|
||||
{ tipo: 'SEMI_CONTADO', claveDescuento: 'semiContado', titulo: 'Semi Contado', descuentoDefault: 0, pctEnganche: 0.5, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
],
|
||||
},
|
||||
SENDA: {
|
||||
id: 'SENDA',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'fechaEntrega',
|
||||
propuestas: [
|
||||
{ tipo: 'CREDITO_BANCARIO', claveDescuento: 'creditoBancario', titulo: 'Crédito Bancario', descuentoDefault: 0, pctEnganche: 0.05, engancheBase: 'final', pctPagoFinal: 0.9, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
{ tipo: 'CREDITO_INFONAVIT_COFINAVIT', claveDescuento: 'creditoInfonavit', titulo: 'Crédito Infonavit-Cofinavit', descuentoDefault: 0, pctEnganche: 0.025, engancheBase: 'final', pctPagoFinal: 0.95, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
{ tipo: 'CONTADO', claveDescuento: 'contado', titulo: 'Contado', descuentoDefault: 0.02, pctEnganche: 0.9, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'ninguna' },
|
||||
{ tipo: 'SEMI_CONTADO', claveDescuento: 'semiContado', titulo: 'Semi Contado', descuentoDefault: 0.01, pctEnganche: 0.5, engancheBase: 'final', pctPagoFinal: 0.05, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
],
|
||||
},
|
||||
SOLANA: {
|
||||
id: 'SOLANA',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'porUnidad',
|
||||
propuestas: [
|
||||
{ tipo: 'CREDITO_BANCARIO', claveDescuento: 'creditoBancario', titulo: 'Crédito Bancario', descuentoDefault: 0, pctEnganche: 0.05, engancheBase: 'final', pctPagoFinal: 0.9, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
{ tipo: 'CREDITO_INFONAVIT_COFINAVIT', claveDescuento: 'creditoInfonavit', titulo: 'Crédito Infonavit-Cofinavit', descuentoDefault: 0, pctEnganche: 0, engancheBase: 'final', pctPagoFinal: 0.95, pagoFinalBase: 'final', modoMensualidades: 'porcentaje', engancheCeroSinApartado: true },
|
||||
{ tipo: 'CONTADO', claveDescuento: 'contado', titulo: 'Contado', descuentoDefault: 0, pctEnganche: 0.9, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'ninguna' },
|
||||
{ tipo: 'SEMI_CONTADO', claveDescuento: 'semiContado', titulo: 'Semi Contado', descuentoDefault: 0, pctEnganche: 0.5, engancheBase: 'final', pctPagoFinal: 0.1, pagoFinalBase: 'final', modoMensualidades: 'porcentaje' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const DESARROLLO_DEFAULT = 'DIAGONAL'
|
||||
|
||||
function obtenerDesarrollo(id?: string | null): ParametrosDesarrollo {
|
||||
return (id && DESARROLLOS[id]) || DESARROLLOS[DESARROLLO_DEFAULT]!
|
||||
}
|
||||
|
||||
export interface PropuestaResultado {
|
||||
tipo: TipoPropuesta
|
||||
titulo: string
|
||||
descuento: number // fracción (0.02 = 2%)
|
||||
montoDescuento: number
|
||||
precioFinal: number // precio lista - descuento
|
||||
enganche: number // NETO del apartado (el apartado ya se pagó)
|
||||
numMensualidades: number
|
||||
mensualidad: number | null // null => N/A (Contado)
|
||||
mensualidadesTotal: number | null // null => N/A (Contado)
|
||||
numMensualidades: number | null // null => plazo desconocido (sin fecha de entrega)
|
||||
mensualidad: number | null // null => N/A (Contado) o plazo desconocido
|
||||
mensualidadesTotal: number | null // null => N/A (Contado) o plazo desconocido
|
||||
pagoFinal: number
|
||||
notaMensualidades?: string
|
||||
}
|
||||
|
||||
export interface CotizacionInput {
|
||||
unidad: UnidadCotizable
|
||||
acabados: boolean // true => incluye cocina y closet
|
||||
acabados: boolean // true => incluye cocina y closet (ignorado en precio único)
|
||||
apartado: number
|
||||
descuentos: DescuentosPropuestas
|
||||
/** Permite forzar N en tests; por defecto se calcula desde fechaEntrega */
|
||||
desarrollo?: string
|
||||
/** Permite forzar N en tests; por defecto se resuelve según la fuenteDeN del desarrollo */
|
||||
numMeses?: number
|
||||
}
|
||||
|
||||
@@ -70,6 +172,21 @@ export function calcularNumMensualidades(fechaEntrega: string, hoy: Date = new D
|
||||
return Math.max(1, Math.round((entrega.getTime() - hoy.getTime()) / MS_POR_MES))
|
||||
}
|
||||
|
||||
/** Resuelve N según la fuente del desarrollo (null si no se puede conocer) */
|
||||
export function resolverNumMensualidades(
|
||||
unidad: UnidadCotizable,
|
||||
params: ParametrosDesarrollo,
|
||||
): number | null {
|
||||
switch (params.fuenteDeN) {
|
||||
case 'fijo':
|
||||
return params.nFijo ?? 1
|
||||
case 'porUnidad':
|
||||
return unidad.mesesParaFirma ?? null
|
||||
case 'fechaEntrega':
|
||||
return unidad.fechaEntrega ? calcularNumMensualidades(unidad.fechaEntrega) : null
|
||||
}
|
||||
}
|
||||
|
||||
function baseComun(precioLista: number, descuento: number) {
|
||||
// Excel COTIZADOR: MONTO DE DESCUENTO y PRECIO FINAL C/DESC
|
||||
const montoDescuento = precioLista * descuento
|
||||
@@ -77,89 +194,60 @@ function baseComun(precioLista: number, descuento: number) {
|
||||
return { montoDescuento, precioFinal }
|
||||
}
|
||||
|
||||
/** PROPUESTA 1 — CRÉDITO BANCARIO */
|
||||
function calcularBancario(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
/** Calcula una propuesta comercial con los parámetros dados */
|
||||
function calcularPropuestaConfig(
|
||||
cfg: PropuestaConfig,
|
||||
precioLista: number,
|
||||
apartado: number,
|
||||
descuento: number,
|
||||
n: number | null,
|
||||
): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M17 — enganche 2.5% sobre precio LISTA, neto del apartado
|
||||
const enganche = 0.025 * precioLista - apartado
|
||||
// Excel COTIZADOR!M19 — pago final 90% del precio final c/desc
|
||||
const pagoFinal = 0.9 * precioFinal
|
||||
// Excel COTIZADOR!M18 — remanente a mensualidades
|
||||
const mensualidadesTotal = precioFinal - apartado - enganche - pagoFinal
|
||||
|
||||
const baseEnganche = cfg.engancheBase === 'lista' ? precioLista : precioFinal
|
||||
const enganche =
|
||||
cfg.engancheCeroSinApartado && cfg.pctEnganche === 0
|
||||
? 0
|
||||
: cfg.pctEnganche * baseEnganche - apartado
|
||||
|
||||
const basePagoFinal = cfg.pagoFinalBase === 'lista' ? precioLista : precioFinal
|
||||
const pagoFinal = cfg.pctPagoFinal * basePagoFinal
|
||||
|
||||
let mensualidadesTotal: number | null = null
|
||||
let mensualidad: number | null = null
|
||||
let notaMensualidades: string | undefined
|
||||
if (cfg.modoMensualidades !== 'ninguna') {
|
||||
if (n === null) {
|
||||
notaMensualidades = 'sin fecha de entrega'
|
||||
} else {
|
||||
switch (cfg.modoMensualidades) {
|
||||
case 'remanente':
|
||||
mensualidadesTotal = precioFinal - apartado - enganche - pagoFinal
|
||||
break
|
||||
case 'remanenteLista':
|
||||
// Quirk DIAGONAL Infonavit: el Excel usa precioLista aquí, NO precioFinal
|
||||
mensualidadesTotal = precioLista - pagoFinal - apartado - enganche
|
||||
break
|
||||
case 'porcentaje':
|
||||
mensualidadesTotal = precioFinal * (1 - cfg.pctEnganche - cfg.pctPagoFinal)
|
||||
break
|
||||
}
|
||||
mensualidad = mensualidadesTotal / n
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
tipo: cfg.tipo,
|
||||
titulo: cfg.titulo,
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: mensualidadesTotal / n,
|
||||
mensualidadesTotal,
|
||||
pagoFinal,
|
||||
}
|
||||
}
|
||||
|
||||
/** PROPUESTA 2 — CRÉDITO INFONAVIT-COFINAVIT */
|
||||
function calcularInfonavit(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M26 — enganche 2.5% sobre precio LISTA, neto del apartado
|
||||
const enganche = 0.025 * precioLista - apartado
|
||||
// Excel COTIZADOR!M28 — pago final 95% del precio final c/desc
|
||||
const pagoFinal = 0.95 * precioFinal
|
||||
// Excel COTIZADOR!M27 — OJO: el Excel usa precioLista aquí, NO precioFinal. Replicar tal cual.
|
||||
const mensualidadesTotal = precioLista - pagoFinal - apartado - enganche
|
||||
return {
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: mensualidadesTotal / n,
|
||||
mensualidadesTotal,
|
||||
pagoFinal,
|
||||
}
|
||||
}
|
||||
|
||||
/** PROPUESTA 3 — CONTADO (sin mensualidades) */
|
||||
function calcularContado(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M36 — enganche 90% del precio final c/desc, neto del apartado
|
||||
const enganche = 0.9 * precioFinal - apartado
|
||||
// Excel COTIZADOR!M37 — pago final 10% del precio final c/desc
|
||||
const pagoFinal = 0.1 * precioFinal
|
||||
return {
|
||||
tipo: 'CONTADO',
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: null, // N/A
|
||||
mensualidadesTotal: null, // N/A
|
||||
pagoFinal,
|
||||
}
|
||||
}
|
||||
|
||||
/** PROPUESTA 4 — SEMI CONTADO */
|
||||
function calcularSemiContado(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M45 — enganche 50% del precio final c/desc, neto del apartado
|
||||
const enganche = 0.5 * precioFinal - apartado
|
||||
// Excel COTIZADOR!M46 — 40% del precio final a mensualidades (= 1 - 0.5 - 0.1)
|
||||
const mensualidadesTotal = precioFinal * 0.4
|
||||
// Excel COTIZADOR!M47 — pago final 10% del precio final c/desc
|
||||
const pagoFinal = 0.1 * precioFinal
|
||||
return {
|
||||
tipo: 'SEMI_CONTADO',
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: mensualidadesTotal / n,
|
||||
mensualidad,
|
||||
mensualidadesTotal,
|
||||
pagoFinal,
|
||||
...(notaMensualidades !== undefined ? { notaMensualidades } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +259,10 @@ export function obtenerPrecioLista(unidad: UnidadCotizable, acabados: boolean):
|
||||
/**
|
||||
* Total pagado por el cliente en una propuesta:
|
||||
* apartado + enganche (neto del apartado) + mensualidades + pago final.
|
||||
* null cuando el total no se puede conocer (plazo desconocido).
|
||||
*/
|
||||
export function totalPagado(p: PropuestaResultado, apartado: number): number {
|
||||
export function totalPagado(p: PropuestaResultado, apartado: number): number | null {
|
||||
if (p.notaMensualidades) return null
|
||||
return apartado + p.enganche + (p.mensualidadesTotal ?? 0) + p.pagoFinal
|
||||
}
|
||||
|
||||
@@ -188,39 +278,32 @@ export function indiceMenorTotal(totales: number[]): number {
|
||||
return indice
|
||||
}
|
||||
|
||||
/** Calcula las 4 propuestas comerciales */
|
||||
/** Calcula las propuestas comerciales del desarrollo de la unidad */
|
||||
export function calcularPropuestas(input: CotizacionInput): PropuestaResultado[] {
|
||||
const { unidad, acabados, apartado, descuentos } = input
|
||||
const params = obtenerDesarrollo(input.desarrollo ?? unidad.desarrollo ?? DESARROLLO_DEFAULT)
|
||||
const precioLista = obtenerPrecioLista(unidad, acabados)
|
||||
const n = input.numMeses ?? calcularNumMensualidades(unidad.fechaEntrega)
|
||||
return [
|
||||
calcularBancario(precioLista, apartado, descuentos.creditoBancario, n),
|
||||
calcularInfonavit(precioLista, apartado, descuentos.creditoInfonavit, n),
|
||||
calcularContado(precioLista, apartado, descuentos.contado, n),
|
||||
calcularSemiContado(precioLista, apartado, descuentos.semiContado, n),
|
||||
]
|
||||
const n = input.numMeses ?? resolverNumMensualidades(unidad, params)
|
||||
return params.propuestas.map((cfg) =>
|
||||
calcularPropuestaConfig(cfg, precioLista, apartado, descuentos[cfg.claveDescuento], n),
|
||||
)
|
||||
}
|
||||
|
||||
/** Mapa entre el nombre del API (body) y el nombre interno de la propuesta */
|
||||
export const TIPO_PROPUESTA_API = {
|
||||
BANCARIO: 'CREDITO_BANCARIO',
|
||||
INFONAVIT: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
COFINAVIT: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
CONTADO: 'CONTADO',
|
||||
SEMI_CONTADO: 'SEMI_CONTADO',
|
||||
} as const
|
||||
|
||||
export type TipoPropuestaApi = keyof typeof TIPO_PROPUESTA_API
|
||||
|
||||
const DESCUENTO_DEFAULT_POR_TIPO: Record<TipoPropuestaApi, number> = {
|
||||
BANCARIO: DESCUENTOS_DEFAULT.creditoBancario,
|
||||
INFONAVIT: DESCUENTOS_DEFAULT.creditoInfonavit,
|
||||
CONTADO: DESCUENTOS_DEFAULT.contado,
|
||||
SEMI_CONTADO: DESCUENTOS_DEFAULT.semiContado,
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula UNA propuesta (la elegida por el cliente) para persistir/cotizar.
|
||||
* `descuento` opcional (fracción); si no viene, usa el default del tipo.
|
||||
* Calcula UNA propuesta (la elegida por el cliente) para persistir/cotizar,
|
||||
* con los parámetros del desarrollo de la unidad.
|
||||
* `descuento` opcional (fracción); si no viene, usa el default de la propuesta.
|
||||
*/
|
||||
export function calcularPropuesta(
|
||||
unidad: UnidadCotizable,
|
||||
@@ -229,17 +312,14 @@ export function calcularPropuesta(
|
||||
apartado: number = APARTADO_DEFAULT,
|
||||
descuento?: number,
|
||||
): PropuestaResultado {
|
||||
const precioLista = obtenerPrecioLista(unidad, acabados)
|
||||
const n = calcularNumMensualidades(unidad.fechaEntrega)
|
||||
const d = descuento ?? DESCUENTO_DEFAULT_POR_TIPO[tipo]
|
||||
switch (tipo) {
|
||||
case 'BANCARIO':
|
||||
return calcularBancario(precioLista, apartado, d, n)
|
||||
case 'INFONAVIT':
|
||||
return calcularInfonavit(precioLista, apartado, d, n)
|
||||
case 'CONTADO':
|
||||
return calcularContado(precioLista, apartado, d, n)
|
||||
case 'SEMI_CONTADO':
|
||||
return calcularSemiContado(precioLista, apartado, d, n)
|
||||
const params = obtenerDesarrollo(unidad.desarrollo)
|
||||
const tipoInterno = TIPO_PROPUESTA_API[tipo]
|
||||
const cfg = params.propuestas.find((p) => p.tipo === tipoInterno)
|
||||
if (!cfg) {
|
||||
throw new Error(`La propuesta ${tipo} no aplica para el desarrollo ${params.id}`)
|
||||
}
|
||||
const precioLista = obtenerPrecioLista(unidad, acabados)
|
||||
const n = resolverNumMensualidades(unidad, params)
|
||||
const d = descuento ?? cfg.descuentoDefault
|
||||
return calcularPropuestaConfig(cfg, precioLista, apartado, d, n)
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ describe('MondayService', () => {
|
||||
precioConAcabados: 4579000,
|
||||
status: 'DISPONIBLE',
|
||||
desarrollo: 'SENDA',
|
||||
mesesParaFirma: null,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -254,17 +254,23 @@ export class MondayService {
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
const status = textoDe('Disponibilidad').toUpperCase()
|
||||
// La columna "Meses para firma" es opcional (solo la usa SOLANA)
|
||||
const colMeses = cols.get('Meses para firma')
|
||||
const mesesTexto = colMeses ? (porId.get(colMeses.id)?.text ?? '') : ''
|
||||
const mesesNum = Number(mesesTexto)
|
||||
return {
|
||||
codigo: item.name,
|
||||
modelo: textoDe('Modelo'),
|
||||
fechaEntrega: textoDe('Fecha entrega'),
|
||||
fechaEntrega: textoDe('Fecha entrega') || null,
|
||||
supEstacionamiento: numeroDe('Sup. estacionamiento'),
|
||||
supConstruccion: numeroDe('Sup. construcción'),
|
||||
supExcedente: numeroDe('Sup. excedente'),
|
||||
precioSinAcabados: numeroDe('Precio sin acabados'),
|
||||
precioConAcabados: numeroDe('Precio con acabados'),
|
||||
status: status === 'APARTADA' || status === 'VENDIDA' ? status : 'DISPONIBLE',
|
||||
// Se conserva el status real del board (VENDIDO, ESCRITURADO, BLOQUEADO, ...)
|
||||
status: status || 'DISPONIBLE',
|
||||
desarrollo: textoDe('Desarrollo'),
|
||||
mesesParaFirma: mesesTexto !== '' && Number.isFinite(mesesNum) ? mesesNum : null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ export function ComparadorPropuestas({ precioLista, apartado, propuestas, titulo
|
||||
const [abierto, setAbierto] = useState(false)
|
||||
|
||||
const totales = propuestas.map((p) => totalPagado(p, apartado))
|
||||
const ganador = indiceMenorTotal(totales)
|
||||
// null (plazo desconocido) nunca gana el menor total
|
||||
const ganador = indiceMenorTotal(totales.map((t) => t ?? Number.POSITIVE_INFINITY))
|
||||
|
||||
const filas: Fila[] = [
|
||||
{ etiqueta: 'Precio de lista', obtener: () => formatoMoneda(precioLista) },
|
||||
@@ -34,19 +35,30 @@ export function ComparadorPropuestas({ precioLista, apartado, propuestas, titulo
|
||||
{
|
||||
etiqueta: 'Mensualidad',
|
||||
obtener: (p) =>
|
||||
p.mensualidad === null
|
||||
? 'N/A'
|
||||
: `${p.numMensualidades} × ${formatoMoneda(p.mensualidad)}`,
|
||||
p.notaMensualidades
|
||||
? `— (${p.notaMensualidades})`
|
||||
: p.mensualidad === null
|
||||
? 'N/A'
|
||||
: `${p.numMensualidades} × ${formatoMoneda(p.mensualidad)}`,
|
||||
},
|
||||
{
|
||||
etiqueta: 'Total mensualidades',
|
||||
obtener: (p) => (p.mensualidadesTotal === null ? 'N/A' : formatoMoneda(p.mensualidadesTotal)),
|
||||
obtener: (p) =>
|
||||
p.notaMensualidades
|
||||
? '—'
|
||||
: p.mensualidadesTotal === null
|
||||
? 'N/A'
|
||||
: formatoMoneda(p.mensualidadesTotal),
|
||||
},
|
||||
{ etiqueta: 'Pago final', obtener: (p) => formatoMoneda(p.pagoFinal) },
|
||||
{ etiqueta: 'Total pagado', obtener: (_p, i) => formatoMoneda(totales[i]), destacada: true },
|
||||
{
|
||||
etiqueta: 'Total pagado',
|
||||
obtener: (_p, i) => (totales[i] === null ? '—' : formatoMoneda(totales[i])),
|
||||
destacada: true,
|
||||
},
|
||||
{
|
||||
etiqueta: 'Diferencia vs precio de lista',
|
||||
obtener: (_p, i) => formatoMoneda(totales[i] - precioLista),
|
||||
obtener: (_p, i) => (totales[i] === null ? '—' : formatoMoneda(totales[i] - precioLista)),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -67,7 +67,9 @@ export function ProposalCard({ numero, titulo, propuesta, onDescuentoChange }: P
|
||||
nota="incluye deducción del apartado"
|
||||
valor={formatoMoneda(propuesta.enganche)}
|
||||
/>
|
||||
{propuesta.mensualidad !== null && propuesta.mensualidadesTotal !== null ? (
|
||||
{propuesta.notaMensualidades ? (
|
||||
<Renglon etiqueta="Mensualidades" nota={propuesta.notaMensualidades} valor="—" />
|
||||
) : propuesta.mensualidad !== null && propuesta.mensualidadesTotal !== null ? (
|
||||
<Renglon
|
||||
etiqueta="Mensualidades"
|
||||
nota={`${propuesta.numMensualidades} × ${formatoMoneda(propuesta.mensualidad)}`}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { formatoFechaCorta } from '../lib/formatters'
|
||||
|
||||
interface Props {
|
||||
unidad: Unidad
|
||||
numMeses: number
|
||||
numMeses: number | null
|
||||
}
|
||||
|
||||
function Fila({ etiqueta, valor }: { etiqueta: string; valor: string }) {
|
||||
@@ -15,8 +15,12 @@ function Fila({ etiqueta, valor }: { etiqueta: string; valor: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
const formatoM2 = (v: number | null): string =>
|
||||
v === null ? '—' : `${v.toLocaleString('es-MX')} m²`
|
||||
|
||||
/** Tarjeta con la ficha técnica de la unidad seleccionada */
|
||||
export function UnitInfoCard({ unidad, numMeses }: Props) {
|
||||
const extras = unidad.extras ?? {}
|
||||
return (
|
||||
<section className="border border-neutral-200 bg-white p-6">
|
||||
<h2 className="mb-4 text-xs font-bold tracking-widest text-neutral-900 uppercase">
|
||||
@@ -24,11 +28,19 @@ export function UnitInfoCard({ unidad, numMeses }: Props) {
|
||||
</h2>
|
||||
<dl className="divide-y divide-neutral-200">
|
||||
<Fila etiqueta="Modelo" valor={unidad.modelo} />
|
||||
<Fila etiqueta="Sup. construcción" valor={`${unidad.supConstruccion.toLocaleString('es-MX')} m²`} />
|
||||
<Fila etiqueta="Sup. excedente" valor={`${unidad.supExcedente.toLocaleString('es-MX')} m²`} />
|
||||
<Fila etiqueta="Sup. estacionamiento" valor={`${unidad.supEstacionamiento.toLocaleString('es-MX')} m²`} />
|
||||
<Fila etiqueta="Fecha de entrega (obra)" valor={formatoFechaCorta(unidad.fechaEntrega)} />
|
||||
<Fila etiqueta="Meses restantes" valor={String(numMeses)} />
|
||||
{extras['prototipo'] != null && <Fila etiqueta="Prototipo" valor={String(extras['prototipo'])} />}
|
||||
{extras['etapa'] != null && <Fila etiqueta="Etapa" valor={String(extras['etapa'])} />}
|
||||
{extras['manzana'] != null && <Fila etiqueta="Manzana" valor={String(extras['manzana'])} />}
|
||||
{extras['lote'] != null && <Fila etiqueta="Lote" valor={String(extras['lote'])} />}
|
||||
{extras['cajones'] != null && <Fila etiqueta="Cajones" valor={String(extras['cajones'])} />}
|
||||
<Fila etiqueta="Sup. construcción" valor={formatoM2(unidad.supConstruccion)} />
|
||||
<Fila etiqueta="Sup. excedente" valor={formatoM2(unidad.supExcedente)} />
|
||||
<Fila etiqueta="Sup. estacionamiento" valor={formatoM2(unidad.supEstacionamiento)} />
|
||||
<Fila
|
||||
etiqueta="Fecha de entrega (obra)"
|
||||
valor={unidad.fechaEntrega ? formatoFechaCorta(unidad.fechaEntrega) : '—'}
|
||||
/>
|
||||
<Fila etiqueta="Meses restantes" valor={numMeses === null ? '—' : String(numMeses)} />
|
||||
</dl>
|
||||
</section>
|
||||
)
|
||||
|
||||
319
apps/cotizador/src/data/desarrollos.ts
Normal file
319
apps/cotizador/src/data/desarrollos.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import type { DescuentosPropuestas, ParametrosDesarrollo } from '../types'
|
||||
|
||||
/**
|
||||
* Parámetros comerciales por desarrollo, replicados de los cotizadores Excel
|
||||
* de producción (ver reference/cotizadores/*.md). NO "corregir" fórmulas:
|
||||
* algunas usan precio de lista donde intuitivamente iría precio final; así
|
||||
* está en los Excel y así debe quedar.
|
||||
*/
|
||||
|
||||
const DIAGONAL: ParametrosDesarrollo = {
|
||||
id: 'DIAGONAL',
|
||||
nombre: 'Diagonal',
|
||||
precioUnico: false,
|
||||
fuenteDeN: 'fechaEntrega',
|
||||
cuotaMantenimiento: '1,500 Aprox.',
|
||||
prefijoUnidadPdf: 'CASA',
|
||||
propuestas: [
|
||||
{
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
claveDescuento: 'creditoBancario',
|
||||
titulo: 'Crédito Bancario',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.025,
|
||||
engancheBase: 'lista',
|
||||
pctPagoFinal: 0.9,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'remanente',
|
||||
},
|
||||
{
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
claveDescuento: 'creditoInfonavit',
|
||||
titulo: 'Crédito Infonavit-Cofinavit',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.025,
|
||||
engancheBase: 'lista',
|
||||
pctPagoFinal: 0.95,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'remanenteLista',
|
||||
},
|
||||
{
|
||||
tipo: 'CONTADO',
|
||||
claveDescuento: 'contado',
|
||||
titulo: 'Contado',
|
||||
descuentoDefault: 0.02,
|
||||
pctEnganche: 0.9,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'ninguna',
|
||||
},
|
||||
{
|
||||
tipo: 'SEMI_CONTADO',
|
||||
claveDescuento: 'semiContado',
|
||||
titulo: 'Semi Contado',
|
||||
descuentoDefault: 0.01,
|
||||
pctEnganche: 0.5,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const ATELIER: ParametrosDesarrollo = {
|
||||
id: 'ATELIER',
|
||||
nombre: 'Atelier',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'fijo', // inventario ya entregado: las mensualidades son 1 solo pago
|
||||
nFijo: 1,
|
||||
cuotaMantenimiento: 'PENDIENTE',
|
||||
prefijoUnidadPdf: 'DEPTO',
|
||||
propuestas: [
|
||||
{
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
claveDescuento: 'creditoBancario',
|
||||
titulo: 'Crédito Bancario',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.05,
|
||||
engancheBase: 'lista', // Excel M36: 5% sobre precio de LISTA
|
||||
pctPagoFinal: 0.9,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'remanente',
|
||||
},
|
||||
{
|
||||
// ATELIER no tiene Infonavit: COFINAVIT con la estructura del Infonavit
|
||||
// de DIAGONAL (2.5% / 95%) pero sin el quirk (todo sobre precio final)
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
claveDescuento: 'creditoInfonavit',
|
||||
titulo: 'Crédito Cofinavit',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.025,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.95,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
{
|
||||
tipo: 'CONTADO',
|
||||
claveDescuento: 'contado',
|
||||
titulo: 'Contado',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.9,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'ninguna',
|
||||
},
|
||||
{
|
||||
tipo: 'SEMI_CONTADO',
|
||||
claveDescuento: 'semiContado',
|
||||
titulo: 'Semi Contado',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.5,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const ESTANZA: ParametrosDesarrollo = {
|
||||
id: 'ESTANZA',
|
||||
nombre: 'Estanza',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'fijo', // TERM. OBRA = 1 mes en todas las casas
|
||||
nFijo: 1,
|
||||
cuotaMantenimiento: 'PENDIENTE',
|
||||
prefijoUnidadPdf: 'CASA',
|
||||
propuestas: [
|
||||
{
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
claveDescuento: 'creditoBancario',
|
||||
titulo: 'Crédito Bancario',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.025,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.9,
|
||||
pagoFinalBase: 'lista', // Excel M20 = C12*H20: pago final sobre LISTA (quirk)
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
{
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
claveDescuento: 'creditoInfonavit',
|
||||
titulo: 'Crédito Infonavit-Cofinavit',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.025,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.95,
|
||||
pagoFinalBase: 'lista', // Excel M26 = H26*C12: pago final sobre LISTA (quirk)
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
{
|
||||
tipo: 'CONTADO',
|
||||
claveDescuento: 'contado',
|
||||
titulo: 'Contado',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.9,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'ninguna',
|
||||
},
|
||||
{
|
||||
tipo: 'SEMI_CONTADO',
|
||||
claveDescuento: 'semiContado',
|
||||
titulo: 'Semi Contado',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.5,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const SENDA: ParametrosDesarrollo = {
|
||||
id: 'SENDA',
|
||||
nombre: 'Senda',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'fechaEntrega', // unidades sin fecha en TERM => N null (mensualidades "—")
|
||||
cuotaMantenimiento: 'PENDIENTE DE DEFINIR',
|
||||
prefijoUnidadPdf: 'CASA',
|
||||
propuestas: [
|
||||
{
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
claveDescuento: 'creditoBancario',
|
||||
titulo: 'Crédito Bancario',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.05,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.9,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
{
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
claveDescuento: 'creditoInfonavit',
|
||||
titulo: 'Crédito Infonavit-Cofinavit',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.025,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.95,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
{
|
||||
tipo: 'CONTADO',
|
||||
claveDescuento: 'contado',
|
||||
titulo: 'Contado',
|
||||
descuentoDefault: 0.02,
|
||||
pctEnganche: 0.9,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'ninguna',
|
||||
},
|
||||
{
|
||||
tipo: 'SEMI_CONTADO',
|
||||
claveDescuento: 'semiContado',
|
||||
titulo: 'Semi Contado',
|
||||
descuentoDefault: 0.01,
|
||||
pctEnganche: 0.5,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.05, // pago final 5% (DIAGONAL: 10%)
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const SOLANA: ParametrosDesarrollo = {
|
||||
id: 'SOLANA',
|
||||
nombre: 'Solana',
|
||||
precioUnico: true,
|
||||
fuenteDeN: 'porUnidad', // MESES PARA FIRMA APROX del inventario (1–4)
|
||||
cuotaMantenimiento: 'PENDIENTE',
|
||||
prefijoUnidadPdf: 'CASA',
|
||||
propuestas: [
|
||||
{
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
claveDescuento: 'creditoBancario',
|
||||
titulo: 'Crédito Bancario',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.05,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.9,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
{
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
claveDescuento: 'creditoInfonavit',
|
||||
titulo: 'Crédito Infonavit-Cofinavit',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.95,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
engancheCeroSinApartado: true, // Excel M27: IF(H27=0, 0, ...)
|
||||
},
|
||||
{
|
||||
tipo: 'CONTADO',
|
||||
claveDescuento: 'contado',
|
||||
titulo: 'Contado',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.9,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'ninguna',
|
||||
},
|
||||
{
|
||||
tipo: 'SEMI_CONTADO',
|
||||
claveDescuento: 'semiContado',
|
||||
titulo: 'Semi Contado',
|
||||
descuentoDefault: 0,
|
||||
pctEnganche: 0.5,
|
||||
engancheBase: 'final',
|
||||
pctPagoFinal: 0.1,
|
||||
pagoFinalBase: 'final',
|
||||
modoMensualidades: 'porcentaje',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const DESARROLLOS: Record<string, ParametrosDesarrollo> = {
|
||||
DIAGONAL,
|
||||
ATELIER,
|
||||
ESTANZA,
|
||||
SENDA,
|
||||
SOLANA,
|
||||
}
|
||||
|
||||
export const DESARROLLO_DEFAULT = 'DIAGONAL'
|
||||
|
||||
/** Ids en orden de presentación en el selector */
|
||||
export const LISTA_DESARROLLOS: string[] = ['DIAGONAL', 'ATELIER', 'ESTANZA', 'SENDA', 'SOLANA']
|
||||
|
||||
/** Parámetros de un desarrollo; cae a DIAGONAL si el id es desconocido */
|
||||
export function obtenerDesarrollo(id?: string | null): ParametrosDesarrollo {
|
||||
return (id && DESARROLLOS[id]) || DESARROLLOS[DESARROLLO_DEFAULT]
|
||||
}
|
||||
|
||||
/** Descuentos por defecto del desarrollo, indexados por clave de propuesta */
|
||||
export function descuentosDefault(id?: string | null): DescuentosPropuestas {
|
||||
const params = obtenerDesarrollo(id)
|
||||
const out: DescuentosPropuestas = {
|
||||
creditoBancario: 0,
|
||||
creditoInfonavit: 0,
|
||||
contado: 0,
|
||||
semiContado: 0,
|
||||
}
|
||||
for (const p of params.propuestas) out[p.claveDescuento] = p.descuentoDefault
|
||||
return out
|
||||
}
|
||||
41
apps/cotizador/src/data/unidades-atelier.ts
Normal file
41
apps/cotizador/src/data/unidades-atelier.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
// Inventario ATELIER — 34 unidades. Departamentos, MZA 32 LOTE 1.
|
||||
// Generado desde reference/cotizadores/ATELIER.json (NO editar a mano; regenerar con scripts/generar-seeds.py).
|
||||
// Precio único por unidad: precioSinAcabados/precioConAcabados replican precioLista.
|
||||
export const UNIDADES_ATELIER: Unidad[] = [
|
||||
{ codigo: 'ATELIER-1', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-01-27', supEstacionamiento: null, supConstruccion: 75.005, supExcedente: 17.468, precioSinAcabados: 3554291, precioConAcabados: 3554291, precioLista: 3554291, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: '2R-A', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
{ codigo: 'ATELIER-2', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-15', supEstacionamiento: null, supConstruccion: 52.5359, supExcedente: 4.1864, precioSinAcabados: 2397434.52, precioConAcabados: 2397434.52, precioLista: 2397434.52, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-A', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-3', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 52.115, supExcedente: null, precioSinAcabados: 2666831, precioConAcabados: 2666831, precioLista: 2666831, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-4', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-28', supEstacionamiento: null, supConstruccion: 53.1241, supExcedente: 4.6853, precioSinAcabados: 2614540, precioConAcabados: 2614540, precioLista: 2614540, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-5', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 52.827, supExcedente: 4.5684, precioSinAcabados: 2614540, precioConAcabados: 2614540, precioLista: 2614540, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-6', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 52.309, supExcedente: 4.326, precioSinAcabados: 2666831, precioConAcabados: 2666831, precioLista: 2666831, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-7', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 52.309, supExcedente: 4.324, precioSinAcabados: 2373713, precioConAcabados: 2373713, precioLista: 2373713, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-8', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 52.059, supExcedente: 4.22, precioSinAcabados: 2327169, precioConAcabados: 2327169, precioLista: 2327169, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-9', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 82.828, supExcedente: 27.913, precioSinAcabados: 4451040, precioConAcabados: 4451040, precioLista: 4451040, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: '2R-B', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
{ codigo: 'ATELIER-10', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-01-21', supEstacionamiento: null, supConstruccion: 83.348, supExcedente: 16.065, precioSinAcabados: 3997500, precioConAcabados: 3997500, precioLista: 3997500, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: '2R-C', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
{ codigo: 'ATELIER-11', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-15', supEstacionamiento: null, supConstruccion: 52.194, supExcedente: 4.219, precioSinAcabados: 2304339, precioConAcabados: 2304339, precioLista: 2304339, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-A', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-12', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-27', supEstacionamiento: null, supConstruccion: 52.526, supExcedente: 4.461, precioSinAcabados: 2281538, precioConAcabados: 2281538, precioLista: 2281538, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-13', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-05', supEstacionamiento: null, supConstruccion: 52.856, supExcedente: 4.575, precioSinAcabados: 2327169, precioConAcabados: 2327169, precioLista: 2327169, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-14', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-30', supEstacionamiento: null, supConstruccion: 52.148, supExcedente: 4.252, precioSinAcabados: 2491122, precioConAcabados: 2491122, precioLista: 2491122, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-15', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-31', supEstacionamiento: null, supConstruccion: 51.772, supExcedente: 4.109, precioSinAcabados: 2261538, precioConAcabados: 2261538, precioLista: 2261538, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-16', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-01-27', supEstacionamiento: null, supConstruccion: 52.371, supExcedente: 4.249, precioSinAcabados: 2327169, precioConAcabados: 2327169, precioLista: 2327169, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-17', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 83.025, supExcedente: 27.495, precioSinAcabados: 4250600, precioConAcabados: 4250600, precioLista: 4250600, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: '2R-B', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
{ codigo: 'ATELIER-18', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-03-24', supEstacionamiento: null, supConstruccion: 75.115, supExcedente: null, precioSinAcabados: 2985326, precioConAcabados: 2985326, precioLista: 2985326, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: '2R-A', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-19', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-21', supEstacionamiento: null, supConstruccion: 52.536, supExcedente: null, precioSinAcabados: 2191183, precioConAcabados: 2191183, precioLista: 2191183, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-A', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-20', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-21', supEstacionamiento: null, supConstruccion: 52.11, supExcedente: 4.2481, precioSinAcabados: 2281538, precioConAcabados: 2281538, precioLista: 2281538, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-21', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-14', supEstacionamiento: null, supConstruccion: 53.056, supExcedente: null, precioSinAcabados: 2506220, precioConAcabados: 2506220, precioLista: 2506220, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-22', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-14', supEstacionamiento: null, supConstruccion: 52.827, supExcedente: null, precioSinAcabados: 2506220, precioConAcabados: 2506220, precioLista: 2506220, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-23', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-09', supEstacionamiento: null, supConstruccion: 52.503, supExcedente: null, precioSinAcabados: 2216961, precioConAcabados: 2216961, precioLista: 2216961, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-24', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-09', supEstacionamiento: null, supConstruccion: 52.501, supExcedente: null, precioSinAcabados: 2666831, precioConAcabados: 2666831, precioLista: 2666831, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-25', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-03-04', supEstacionamiento: null, supConstruccion: 52.085, supExcedente: null, precioSinAcabados: 2173491, precioConAcabados: 2173491, precioLista: 2173491, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-26', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-03-04', supEstacionamiento: null, supConstruccion: 82.805, supExcedente: null, precioSinAcabados: 3547406, precioConAcabados: 3547406, precioLista: 3547406, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: '2R-B', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
{ codigo: 'ATELIER-27', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-03-14', supEstacionamiento: null, supConstruccion: 82.633, supExcedente: null, precioSinAcabados: 3750500, precioConAcabados: 3750500, precioLista: 3750500, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: '2R-C', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
{ codigo: 'ATELIER-28', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-07', supEstacionamiento: null, supConstruccion: 52.242, supExcedente: null, precioSinAcabados: 2059713, precioConAcabados: 2059713, precioLista: 2059713, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-A', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-29', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-28', supEstacionamiento: null, supConstruccion: 52.518, supExcedente: null, precioSinAcabados: 2579442, precioConAcabados: 2579442, precioLista: 2579442, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-30', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-12', supEstacionamiento: null, supConstruccion: 52.958, supExcedente: null, precioSinAcabados: 2173491, precioConAcabados: 2173491, precioLista: 2173491, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-31', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-17', supEstacionamiento: null, supConstruccion: 52.101, supExcedente: null, precioSinAcabados: 2216961, precioConAcabados: 2216961, precioLista: 2216961, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-32', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-25', supEstacionamiento: null, supConstruccion: 51.83, supExcedente: null, precioSinAcabados: 2173491, precioConAcabados: 2173491, precioLista: 2173491, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-33', modelo: 'SOHO', desarrollo: 'ATELIER', fechaEntrega: '2026-02-25', supEstacionamiento: null, supConstruccion: 52.25, supExcedente: null, precioSinAcabados: 2151756.09, precioConAcabados: 2151756.09, precioLista: 2151756.09, status: 'ESCRITURADO', mesesParaFirma: null, extras: { prototipo: 'E-B', manzana: 32, lote: 1, cajones: '1 C-INT' } },
|
||||
{ codigo: 'ATELIER-34', modelo: 'TRIBECA', desarrollo: 'ATELIER', fechaEntrega: '2026-02-28', supEstacionamiento: null, supConstruccion: 83.154, supExcedente: null, precioSinAcabados: 3750500, precioConAcabados: 3750500, precioLista: 3750500, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: '2R-B', manzana: 32, lote: 1, cajones: '1 C-EXT' } },
|
||||
]
|
||||
71
apps/cotizador/src/data/unidades-estanza.ts
Normal file
71
apps/cotizador/src/data/unidades-estanza.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
// Inventario ESTANZA — 64 unidades. Casas DUPLO A/B, MZA 92 LOTE 32.
|
||||
// Generado desde reference/cotizadores/ESTANZA.json (NO editar a mano; regenerar con scripts/generar-seeds.py).
|
||||
// Precio único por unidad: precioSinAcabados/precioConAcabados replican precioLista.
|
||||
export const UNIDADES_ESTANZA: Unidad[] = [
|
||||
{ codigo: 'ESTANZA-1', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 27.061, precioSinAcabados: 2617632, precioConAcabados: 2617632, precioLista: 2617632, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-2', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: 14.6227, precioSinAcabados: 2653052, precioConAcabados: 2653052, precioLista: 2653052, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-3', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 10.3277, precioSinAcabados: 2273327, precioConAcabados: 2273327, precioLista: 2273327, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-4', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: 8.1628, precioSinAcabados: 2558590, precioConAcabados: 2558590, precioLista: 2558590, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-5', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 4.4683, precioSinAcabados: 2292631, precioConAcabados: 2292631, precioLista: 2292631, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-6', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: 2.394, precioSinAcabados: 2536885, precioConAcabados: 2536885, precioLista: 2536885, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-7', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 1.0732, precioSinAcabados: 2486023, precioConAcabados: 2486023, precioLista: 2486023, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-8', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: 1.4823, precioSinAcabados: 2485590, precioConAcabados: 2485590, precioLista: 2485590, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-9', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 1.5349, precioSinAcabados: 2489942, precioConAcabados: 2489942, precioLista: 2489942, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-10', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: 1.1014, precioSinAcabados: 2340562, precioConAcabados: 2340562, precioLista: 2340562, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-11', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2501683, precioConAcabados: 2501683, precioLista: 2501683, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-12', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2512571, precioConAcabados: 2512571, precioLista: 2512571, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-13', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2472677, precioConAcabados: 2472677, precioLista: 2472677, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-14', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2508175, precioConAcabados: 2508175, precioLista: 2508175, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-15', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2361751, precioConAcabados: 2361751, precioLista: 2361751, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-16', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2589780, precioConAcabados: 2589780, precioLista: 2589780, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-17', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-18', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2407848, precioConAcabados: 2407848, precioLista: 2407848, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-19', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-20', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2563074, precioConAcabados: 2563074, precioLista: 2563074, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-21', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2528824, precioConAcabados: 2528824, precioLista: 2528824, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-22', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2241564, precioConAcabados: 2241564, precioLista: 2241564, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-23', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2410854, precioConAcabados: 2410854, precioLista: 2410854, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-24', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2345652, precioConAcabados: 2345652, precioLista: 2345652, status: 'VENDIDO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-25', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-26', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2563074, precioConAcabados: 2563074, precioLista: 2563074, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-27', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2497404, precioConAcabados: 2497404, precioLista: 2497404, status: 'VENDIDO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-28', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2512571, precioConAcabados: 2512571, precioLista: 2512571, status: 'VENDIDO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-29', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2492611, precioConAcabados: 2492611, precioLista: 2492611, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-30', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2516765, precioConAcabados: 2516765, precioLista: 2516765, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-31', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 18.296, precioSinAcabados: 2521121, precioConAcabados: 2521121, precioLista: 2521121, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-32', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2412850, precioConAcabados: 2412850, precioLista: 2412850, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-33', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-34', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2537697, precioConAcabados: 2537697, precioLista: 2537697, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-35', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2380999, precioConAcabados: 2380999, precioLista: 2380999, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-36', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2380999, precioConAcabados: 2380999, precioLista: 2380999, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-37', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-38', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2427873, precioConAcabados: 2427873, precioLista: 2427873, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-39', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2485590, precioConAcabados: 2485590, precioLista: 2485590, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-40', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2506594, precioConAcabados: 2506594, precioLista: 2506594, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-41', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-42', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2515999, precioConAcabados: 2515999, precioLista: 2515999, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-43', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2443465, precioConAcabados: 2443465, precioLista: 2443465, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-44', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2287855, precioConAcabados: 2287855, precioLista: 2287855, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-45', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2269258, precioConAcabados: 2269258, precioLista: 2269258, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-46', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: 6.3751, precioSinAcabados: 2581017, precioConAcabados: 2581017, precioLista: 2581017, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-47', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: 27.7262, precioSinAcabados: 2752486, precioConAcabados: 2752486, precioLista: 2752486, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-48', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2581017, precioConAcabados: 2581017, precioLista: 2581017, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-49', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2516765, precioConAcabados: 2516765, precioLista: 2516765, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-50', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2529223, precioConAcabados: 2529223, precioLista: 2529223, status: 'VENDIDO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-51', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2490390, precioConAcabados: 2490390, precioLista: 2490390, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-52', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2437250, precioConAcabados: 2437250, precioLista: 2437250, status: 'APARTADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-53', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2345652, precioConAcabados: 2345652, precioLista: 2345652, status: 'APARTADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-54', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2556059, precioConAcabados: 2556059, precioLista: 2556059, status: 'VENDIDO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-55', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2492920, precioConAcabados: 2492920, precioLista: 2492920, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-56', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2533257, precioConAcabados: 2533257, precioLista: 2533257, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-57', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2492920, precioConAcabados: 2492920, precioLista: 2492920, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-58', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2529223, precioConAcabados: 2529223, precioLista: 2529223, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-59', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2388920, precioConAcabados: 2388920, precioLista: 2388920, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-60', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2558590, precioConAcabados: 2558590, precioLista: 2558590, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-61', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2345652, precioConAcabados: 2345652, precioLista: 2345652, status: 'DISPONIBLE', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-62', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2537697, precioConAcabados: 2537697, precioLista: 2537697, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-63', modelo: 'DUPLO A', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.06, supExcedente: null, precioSinAcabados: 2248242, precioConAcabados: 2248242, precioLista: 2248242, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
{ codigo: 'ESTANZA-64', modelo: 'DUPLO B', desarrollo: 'ESTANZA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.18, supExcedente: null, precioSinAcabados: 2457269, precioConAcabados: 2457269, precioLista: 2457269, status: 'ESCRITURADO', mesesParaFirma: null, extras: { manzana: 92, lote: 32, cajones: 1 } },
|
||||
]
|
||||
146
apps/cotizador/src/data/unidades-senda.ts
Normal file
146
apps/cotizador/src/data/unidades-senda.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
// Inventario SENDA — 139 unidades. Casas; fechaEntrega null cuando la unidad no está en la tabla TERM (mensualidades no calculables).
|
||||
// Generado desde reference/cotizadores/SENDA.json (NO editar a mano; regenerar con scripts/generar-seeds.py).
|
||||
// Precio único por unidad: precioSinAcabados/precioConAcabados replican precioLista.
|
||||
export const UNIDADES_SENDA: Unidad[] = [
|
||||
{ codigo: 'SENDA-411-1-2', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-07', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2287000, precioConAcabados: 2287000, precioLista: 2287000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-10', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-02', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-11', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-09', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-12', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-16', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2468483, precioConAcabados: 2468483, precioLista: 2468483, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-13', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-23', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-14', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-01-30', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-2', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-07', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-3', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-14', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2468483, precioConAcabados: 2468483, precioLista: 2468483, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-4', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-21', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-5', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-28', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-6', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-05', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2468483, precioConAcabados: 2468483, precioLista: 2468483, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-26-7', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-12', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2450000, precioConAcabados: 2450000, precioLista: 2450000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-3-5', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-401-3-6', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-401-3-2', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-411-2-2', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 411, lote: 2, cajones: 2 } },
|
||||
{ codigo: 'SENDA-401-26-1', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-10-24', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-3-7', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-401-26-9', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-26', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2450000, precioConAcabados: 2450000, precioLista: 2450000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-3-3', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-401-3-8', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-401-26-8', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-19', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 401, lote: 26, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-4', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-21', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2287000, precioConAcabados: 2287000, precioLista: 2287000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-2', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-09-04', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-3', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-09-11', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-4', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-09-18', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-5', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-09-25', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-6', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-10-02', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-7', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-10-09', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-8', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-10-16', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-411-1-13', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-23', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-10', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-10-30', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-11', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-11-06', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-2-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-12-04', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 2, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-13', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-04-09', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-14', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-04-02', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-15', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-03-26', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-16', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-03-19', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-17', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-03-12', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-18', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-03-05', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-19', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-02-26', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-20', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-02-19', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-21', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-02-12', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-22', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-02-05', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-23', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-01-29', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-24', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-01-22', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-25', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-01-15', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-26', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-01-08', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-27', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2029-01-01', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-28', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-12-25', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-29', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-12-18', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-30', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-12-04', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 409, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-3-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-12-11', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-410-2-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-07-17', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 410, lote: 2, cajones: 2 } },
|
||||
{ codigo: 'SENDA-410-3-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-07-24', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 410, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-410-1-11', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2450000, precioConAcabados: 2450000, precioLista: 2450000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-410-1-8', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-9', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-10', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-12', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-16', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2318246, precioConAcabados: 2318246, precioLista: 2318246, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-12', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-13', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-14', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-15', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-16', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-17', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-02-13', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-18', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-02-20', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-19', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-10-09', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-20', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-10-02', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-21', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-09-25', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-22', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-09-18', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-23', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-09-11', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-24', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-09-04', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-25', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-08-28', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-26', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-08-21', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-27', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-08-14', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-28', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-08-07', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-29', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-07-31', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-30', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-07-24', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-31', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2028-07-17', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'BLOQUEADO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-7A', modelo: 'SANTA CRUZ', desarrollo: 'SENDA', fechaEntrega: '2027-03-20', supEstacionamiento: null, supConstruccion: null, supExcedente: null, precioSinAcabados: 2063666.75, precioConAcabados: 2063666.75, precioLista: 2063666.75, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'DUPLO', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-A102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-A101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-A201', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-B101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-B102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-5', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-28', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-B202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-C101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-C102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-C201', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-C202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-D101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-D102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-D201', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-D202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-E101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-E102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-E201', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-E202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-F101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-F102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-F201', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-F202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-G101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-G202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-5-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-11-20', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 5, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-4-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-11-27', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 4, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-08-28', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-411-1-1', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-10-24', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2326593, precioConAcabados: 2326593, precioLista: 2326593, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-409-1-12', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-11-13', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-410-1-15A', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: '2027-05-15', supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 0, precioConAcabados: 0, precioLista: null, status: 'APARTADO', mesesParaFirma: null, extras: { prototipo: 'DUPLO', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-3', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-11-14', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-11', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-09', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-6', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-05', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-7', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-12', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-8', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-19', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-9', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2026-12-26', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-401-3-4', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 401, lote: 3, cajones: 2 } },
|
||||
{ codigo: 'SENDA-409-1-9', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2027-10-23', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2700000, precioConAcabados: 2700000, precioLista: 2700000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 409, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-411-1-10', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-01-02', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-14', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-04-17', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-23', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-02-13', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-16', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-04-03', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-15', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-04-10', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-17', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-03-27', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-18', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-03-20', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-19', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-03-13', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-20', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-03-06', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-21', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-02-27', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-22', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-02-20', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-410-1-16B', modelo: 'SANTA CRUZ', desarrollo: 'SENDA', fechaEntrega: '2027-05-15', supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 2112663.75, precioConAcabados: 2112663.75, precioLista: 2112663.75, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'DUPLO', manzana: 410, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-1-24', modelo: 'HIMALAYA', desarrollo: 'SENDA', fechaEntrega: '2027-02-06', supEstacionamiento: null, supConstruccion: 69.33, supExcedente: null, precioSinAcabados: 2451750, precioConAcabados: 2451750, precioLista: 2451750, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'UNE-C', manzana: 411, lote: 1, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-25-A201', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 411, lote: 25, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-25-A202', modelo: 'ESMERALDA PA', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.78, supExcedente: null, precioSinAcabados: 1850000, precioConAcabados: 1850000, precioLista: 1850000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 411, lote: 25, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-25-A101', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 411, lote: 25, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-25-A102', modelo: 'ESMERALDA PB', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 55.2, supExcedente: null, precioSinAcabados: 1948000, precioConAcabados: 1948000, precioLista: 1948000, status: 'DISPONIBLE', mesesParaFirma: null, extras: { prototipo: 'CUADRUPLEX', manzana: 411, lote: 25, cajones: 1 } },
|
||||
{ codigo: 'SENDA-411-26-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2750000, precioConAcabados: 2750000, precioLista: 2750000, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 411, lote: 1, cajones: 2 } },
|
||||
{ codigo: 'SENDA-411-3-1', modelo: 'SANTIAGO', desarrollo: 'SENDA', fechaEntrega: '2026-12-05', supEstacionamiento: null, supConstruccion: 63.95, supExcedente: null, precioSinAcabados: 2524241, precioConAcabados: 2524241, precioLista: 2524241, status: 'VENDIDO', mesesParaFirma: null, extras: { prototipo: 'LAUREL', manzana: 411, lote: 3, cajones: 2 } },
|
||||
]
|
||||
17
apps/cotizador/src/data/unidades-solana.ts
Normal file
17
apps/cotizador/src/data/unidades-solana.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
// Inventario SOLANA — 10 unidades. Casas etapas SOLANA I/II; mesesParaFirma (1-4) es el plazo de mensualidades.
|
||||
// Generado desde reference/cotizadores/SOLANA.json (NO editar a mano; regenerar con scripts/generar-seeds.py).
|
||||
// Precio único por unidad: precioSinAcabados/precioConAcabados replican precioLista.
|
||||
export const UNIDADES_SOLANA: Unidad[] = [
|
||||
{ codigo: 'SOLANA-1A', modelo: 'PANORAMA', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: 33.8094, precioSinAcabados: 2990445, precioConAcabados: 2990445, precioLista: 2990445, status: 'ESCRITURADO', mesesParaFirma: 3, extras: { prototipo: 'UNE-C', etapa: 'SOLANA I', cajones: 2 } },
|
||||
{ codigo: 'SOLANA-2A', modelo: 'PANORAMA', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: 4.95, precioSinAcabados: 2767518, precioConAcabados: 2767518, precioLista: 2767518, status: 'VENDIDO', mesesParaFirma: 2, extras: { prototipo: 'UNE-C', etapa: 'SOLANA I', cajones: 2 } },
|
||||
{ codigo: 'SOLANA-3A', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: 26.647, precioSinAcabados: 2922847, precioConAcabados: 2922847, precioLista: 2922847, status: 'ESCRITURADO', mesesParaFirma: 3, extras: { prototipo: 'UNE-C', etapa: 'SOLANA I', cajones: 1 } },
|
||||
{ codigo: 'SOLANA-4A', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: 27.815, precioSinAcabados: 2952847, precioConAcabados: 2952847, precioLista: 2952847, status: 'ESCRITURADO', mesesParaFirma: 4, extras: { prototipo: 'UNE-C', etapa: 'SOLANA I', cajones: 1 } },
|
||||
{ codigo: 'SOLANA-5A', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.83, supExcedente: 22.067, precioSinAcabados: 3400000, precioConAcabados: 3400000, precioLista: 3400000, status: 'ESCRITURADO', mesesParaFirma: 4, extras: { prototipo: 'LAUREL', etapa: 'SOLANA I', cajones: 1 } },
|
||||
{ codigo: 'SOLANA-6', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 69.33, supExcedente: 0, precioSinAcabados: 2560000, precioConAcabados: 2560000, precioLista: 2560000, status: 'DISPONIBLE', mesesParaFirma: 1, extras: { prototipo: 'UNE-C', etapa: 'SOLANA II', cajones: 1 } },
|
||||
{ codigo: 'SOLANA-7', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.83, supExcedente: 0, precioSinAcabados: 2595000, precioConAcabados: 2595000, precioLista: 2595000, status: 'DISPONIBLE', mesesParaFirma: 1, extras: { prototipo: 'DUPLO-B', etapa: 'SOLANA II', cajones: 2 } },
|
||||
{ codigo: 'SOLANA-8', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 56.17, supExcedente: 0, precioSinAcabados: 2575000, precioConAcabados: 2575000, precioLista: 2575000, status: 'VENDIDO', mesesParaFirma: 1, extras: { prototipo: 'DUPLO-A', etapa: 'SOLANA II', cajones: 2 } },
|
||||
{ codigo: 'SOLANA-9', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 63.83, supExcedente: 0, precioSinAcabados: 2605000, precioConAcabados: 2605000, precioLista: 2605000, status: 'DISPONIBLE', mesesParaFirma: 1, extras: { prototipo: 'DUPLO-B', etapa: 'SOLANA II', cajones: 2 } },
|
||||
{ codigo: 'SOLANA-10', modelo: 'MIRADOR', desarrollo: 'SOLANA', fechaEntrega: null, supEstacionamiento: null, supConstruccion: 56.17, supExcedente: 0, precioSinAcabados: 2730000, precioConAcabados: 2730000, precioLista: 2730000, status: 'VENDIDO', mesesParaFirma: 1, extras: { prototipo: 'DUPLO-A', etapa: 'SOLANA II', cajones: 2 } },
|
||||
]
|
||||
@@ -1,43 +1,57 @@
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
// Inventario real — 38 unidades, todas DISPONIBLE
|
||||
import { UNIDADES_ATELIER } from './unidades-atelier'
|
||||
import { UNIDADES_ESTANZA } from './unidades-estanza'
|
||||
import { UNIDADES_SENDA } from './unidades-senda'
|
||||
import { UNIDADES_SOLANA } from './unidades-solana'
|
||||
|
||||
export const UNIDADES_SEED: Unidad[] = [
|
||||
{ codigo: 'U-101', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 21.336, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-102', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-103', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-104', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-105', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-106', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-107', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 20.842, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-108', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 18.97, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-109', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-110', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-111', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-112', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-113', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-114', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 18.476, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-201', modelo: '2RA', fechaEntrega: '2027-10-15', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-202', modelo: '1R', fechaEntrega: '2027-10-15', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-203', modelo: '1R', fechaEntrega: '2027-10-22', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-204', modelo: '1R', fechaEntrega: '2027-10-22', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-205', modelo: '1R', fechaEntrega: '2027-10-22', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-206', modelo: '1R', fechaEntrega: '2027-10-29', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-207', modelo: '2RA', fechaEntrega: '2027-10-29', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-208', modelo: '2RA', fechaEntrega: '2027-10-29', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-209', modelo: '1R', fechaEntrega: '2027-10-29', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-210', modelo: '1R', fechaEntrega: '2027-10-29', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-211', modelo: '1R', fechaEntrega: '2027-11-05', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-212', modelo: '1R', fechaEntrega: '2027-11-05', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-213', modelo: '1R', fechaEntrega: '2027-11-12', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-214', modelo: '2RA', fechaEntrega: '2027-11-12', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-301', modelo: '2RA', fechaEntrega: '2027-11-12', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-302', modelo: '2RB', fechaEntrega: '2027-11-12', supEstacionamiento: 28.62, supConstruccion: 68.757, supExcedente: 0, precioSinAcabados: 4120000, precioConAcabados: 4280000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-303', modelo: '2RC', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-304', modelo: '2RC', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-305', modelo: '2RA', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-306', modelo: '2RA', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-307', modelo: '2RC', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-308', modelo: '2RC', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-309', modelo: '2RB', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 68.757, supExcedente: 0, precioSinAcabados: 4120000, precioConAcabados: 4280000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-310', modelo: '2RA', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE' },
|
||||
{ codigo: 'U-101', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 21.336, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-102', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-103', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-104', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-105', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-106', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.132, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-107', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 20.842, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-108', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 18.97, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-109', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-110', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-111', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-112', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-113', modelo: '1R', fechaEntrega: '2027-10-08', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 9.13, precioSinAcabados: 3107108.39, precioConAcabados: 3269000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-114', modelo: '2RA', fechaEntrega: '2027-10-08', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 18.476, precioSinAcabados: 4415000, precioConAcabados: 4579000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-201', modelo: '2RA', fechaEntrega: '2027-10-15', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-202', modelo: '1R', fechaEntrega: '2027-10-15', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-203', modelo: '1R', fechaEntrega: '2027-10-22', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-204', modelo: '1R', fechaEntrega: '2027-10-22', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-205', modelo: '1R', fechaEntrega: '2027-10-22', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-206', modelo: '1R', fechaEntrega: '2027-10-29', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-207', modelo: '2RA', fechaEntrega: '2027-10-29', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-208', modelo: '2RA', fechaEntrega: '2027-10-29', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-209', modelo: '1R', fechaEntrega: '2027-10-29', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-210', modelo: '1R', fechaEntrega: '2027-10-29', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-211', modelo: '1R', fechaEntrega: '2027-11-05', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-212', modelo: '1R', fechaEntrega: '2027-11-05', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-213', modelo: '1R', fechaEntrega: '2027-11-12', supEstacionamiento: 14.31, supConstruccion: 47.87, supExcedente: 0, precioSinAcabados: 2850000, precioConAcabados: 2950000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-214', modelo: '2RA', fechaEntrega: '2027-11-12', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 3960000, precioConAcabados: 4127000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-301', modelo: '2RA', fechaEntrega: '2027-11-12', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-302', modelo: '2RB', fechaEntrega: '2027-11-12', supEstacionamiento: 28.62, supConstruccion: 68.757, supExcedente: 0, precioSinAcabados: 4120000, precioConAcabados: 4280000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-303', modelo: '2RC', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-304', modelo: '2RC', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-305', modelo: '2RA', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-306', modelo: '2RA', fechaEntrega: '2027-11-19', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-307', modelo: '2RC', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-308', modelo: '2RC', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 72.328, supExcedente: 0, precioSinAcabados: 4300000, precioConAcabados: 4450000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-309', modelo: '2RB', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 68.757, supExcedente: 0, precioSinAcabados: 4120000, precioConAcabados: 4280000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
{ codigo: 'U-310', modelo: '2RA', fechaEntrega: '2027-11-26', supEstacionamiento: 28.62, supConstruccion: 69.485, supExcedente: 0, precioSinAcabados: 4150000, precioConAcabados: 4315000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL' },
|
||||
]
|
||||
|
||||
/** Inventario local completo (fallback cuando el API no responde) */
|
||||
export const INVENTARIO_SEED: Unidad[] = [
|
||||
...UNIDADES_SEED,
|
||||
...UNIDADES_ATELIER,
|
||||
...UNIDADES_ESTANZA,
|
||||
...UNIDADES_SENDA,
|
||||
...UNIDADES_SOLANA,
|
||||
]
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import type { Unidad, DescuentosPropuestas } from '../types'
|
||||
import type { Unidad, DescuentosPropuestas, ParametrosDesarrollo } from '../types'
|
||||
import {
|
||||
APARTADO_DEFAULT,
|
||||
DESCUENTOS_DEFAULT,
|
||||
calcularNumMensualidades,
|
||||
calcularPropuestas,
|
||||
obtenerPrecioLista,
|
||||
resolverNumMensualidades,
|
||||
type PropuestaResultado,
|
||||
} from '../lib/calculations'
|
||||
import { DESARROLLO_DEFAULT, descuentosDefault, obtenerDesarrollo } from '../data/desarrollos'
|
||||
|
||||
interface UseCotizacionResult {
|
||||
desarrollo: string
|
||||
/** Cambia de desarrollo: limpia la unidad elegida y reinicia los descuentos */
|
||||
setDesarrollo: (id: string) => void
|
||||
params: ParametrosDesarrollo
|
||||
unidad: Unidad | null
|
||||
setUnidad: (u: Unidad | null) => void
|
||||
acabados: boolean
|
||||
@@ -19,24 +23,34 @@ interface UseCotizacionResult {
|
||||
descuentos: DescuentosPropuestas
|
||||
setDescuento: (clave: keyof DescuentosPropuestas, valor: number) => void
|
||||
precioLista: number
|
||||
numMeses: number
|
||||
numMeses: number | null
|
||||
propuestas: PropuestaResultado[]
|
||||
}
|
||||
|
||||
/** Estado central de la cotización: unidad, acabados, apartado y descuentos */
|
||||
/** Estado central de la cotización: desarrollo, unidad, acabados, apartado y descuentos */
|
||||
export function useCotizacion(): UseCotizacionResult {
|
||||
const [desarrollo, setDesarrolloState] = useState<string>(DESARROLLO_DEFAULT)
|
||||
const [unidad, setUnidad] = useState<Unidad | null>(null)
|
||||
const [acabados, setAcabados] = useState(false)
|
||||
const [apartado, setApartado] = useState<number>(APARTADO_DEFAULT)
|
||||
const [descuentos, setDescuentos] = useState<DescuentosPropuestas>({ ...DESCUENTOS_DEFAULT })
|
||||
const [descuentos, setDescuentos] = useState<DescuentosPropuestas>(descuentosDefault(DESARROLLO_DEFAULT))
|
||||
|
||||
const params = obtenerDesarrollo(desarrollo)
|
||||
|
||||
const setDesarrollo = (id: string) => {
|
||||
setDesarrolloState(id)
|
||||
setUnidad(null)
|
||||
setAcabados(false)
|
||||
setDescuentos(descuentosDefault(id))
|
||||
}
|
||||
|
||||
const setDescuento = (clave: keyof DescuentosPropuestas, valor: number) => {
|
||||
setDescuentos((prev) => ({ ...prev, [clave]: valor }))
|
||||
}
|
||||
|
||||
const numMeses = useMemo(
|
||||
() => (unidad ? calcularNumMensualidades(unidad.fechaEntrega) : 0),
|
||||
[unidad],
|
||||
() => (unidad ? resolverNumMensualidades(unidad, params) : null),
|
||||
[unidad, params],
|
||||
)
|
||||
|
||||
const precioLista = useMemo(
|
||||
@@ -46,10 +60,13 @@ export function useCotizacion(): UseCotizacionResult {
|
||||
|
||||
const propuestas = useMemo(() => {
|
||||
if (!unidad) return []
|
||||
return calcularPropuestas({ unidad, acabados, apartado, descuentos })
|
||||
}, [unidad, acabados, apartado, descuentos])
|
||||
return calcularPropuestas({ unidad, acabados, apartado, descuentos, desarrollo })
|
||||
}, [unidad, acabados, apartado, descuentos, desarrollo])
|
||||
|
||||
return {
|
||||
desarrollo,
|
||||
setDesarrollo,
|
||||
params,
|
||||
unidad,
|
||||
setUnidad,
|
||||
acabados,
|
||||
|
||||
291
apps/cotizador/src/lib/calculations-desarrollos.test.ts
Normal file
291
apps/cotizador/src/lib/calculations-desarrollos.test.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
calcularPropuestas,
|
||||
obtenerPrecioLista,
|
||||
resolverNumMensualidades,
|
||||
totalPagado,
|
||||
APARTADO_DEFAULT,
|
||||
} from './calculations'
|
||||
import { DESARROLLOS, descuentosDefault, obtenerDesarrollo } from '../data/desarrollos'
|
||||
import { UNIDADES_ATELIER } from '../data/unidades-atelier'
|
||||
import { UNIDADES_ESTANZA } from '../data/unidades-estanza'
|
||||
import { UNIDADES_SENDA } from '../data/unidades-senda'
|
||||
import { UNIDADES_SOLANA } from '../data/unidades-solana'
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
/**
|
||||
* Fixtures validados al centavo contra los Excel de producción
|
||||
* (ver reference/cotizadores/{ATELIER,ESTANZA,SENDA,SOLANA}.md).
|
||||
* Orden de propuestas en la config: [bancario, infonavit/cofinavit, contado, semi].
|
||||
*/
|
||||
|
||||
function porCodigo(unidades: Unidad[], codigo: string): Unidad {
|
||||
const u = unidades.find((x) => x.codigo === codigo)
|
||||
if (!u) throw new Error(`Fixture ${codigo} no existe en el seed`)
|
||||
return u
|
||||
}
|
||||
|
||||
describe('ATELIER — depto 10 (L = 3,997,500, apartado 20,000, N fijo = 1)', () => {
|
||||
const unidad = porCodigo(UNIDADES_ATELIER, 'ATELIER-10')
|
||||
const calcular = () =>
|
||||
calcularPropuestas({
|
||||
unidad,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: descuentosDefault('ATELIER'),
|
||||
})
|
||||
|
||||
it('precio único: no depende de acabados', () => {
|
||||
expect(obtenerPrecioLista(unidad, true)).toBe(3997500)
|
||||
expect(obtenerPrecioLista(unidad, false)).toBe(3997500)
|
||||
})
|
||||
|
||||
it('resuelve N = 1 fijo (entrega inmediata)', () => {
|
||||
expect(resolverNumMensualidades(unidad, DESARROLLOS['ATELIER'])).toBe(1)
|
||||
})
|
||||
|
||||
it('BANCARIO: enganche 5% sobre LISTA; mensualidad = remanente en 1 pago', () => {
|
||||
const [p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(179875, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(199875, 2)
|
||||
expect(p.mensualidad).toBeCloseTo(199875, 2)
|
||||
expect(p.numMensualidades).toBe(1)
|
||||
expect(p.pagoFinal).toBeCloseTo(3597750, 2)
|
||||
})
|
||||
|
||||
it('COFINAVIT: estructura Infonavit sin quirk (todo sobre precio final)', () => {
|
||||
const [, p] = calcular()
|
||||
expect(p.titulo).toBe('Crédito Cofinavit')
|
||||
expect(p.enganche).toBeCloseTo(79937.5, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(99937.5, 2)
|
||||
expect(p.mensualidad).toBeCloseTo(99937.5, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(3797625, 2)
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(3997500, 2)
|
||||
})
|
||||
|
||||
it('CONTADO', () => {
|
||||
const [, , p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(3577750, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(399750, 2)
|
||||
expect(p.mensualidad).toBeNull()
|
||||
})
|
||||
|
||||
it('SEMI CONTADO', () => {
|
||||
const [, , , p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(1978750, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(1599000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(399750, 2)
|
||||
})
|
||||
|
||||
it('bancario cuadra: apartado + enganche + mensualidades + pago final = precio final', () => {
|
||||
const [p] = calcular()
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(3997500, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ESTANZA — casa 52 (L = 2,437,250, apartado 20,000, N fijo = 1, desc 0)', () => {
|
||||
const unidad = porCodigo(UNIDADES_ESTANZA, 'ESTANZA-52')
|
||||
const calcular = () =>
|
||||
calcularPropuestas({
|
||||
unidad,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: descuentosDefault('ESTANZA'),
|
||||
})
|
||||
|
||||
it('BANCARIO: enganche 2.5% sobre final; pago final 90% sobre LISTA (quirk)', () => {
|
||||
const [p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(40931.25, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(182793.75, 2)
|
||||
expect(p.mensualidad).toBeCloseTo(182793.75, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2193525, 2)
|
||||
})
|
||||
|
||||
it('INFONAVIT: pago final 95% sobre LISTA (quirk)', () => {
|
||||
const [, p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(40931.25, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(60931.25, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2315387.5, 2)
|
||||
})
|
||||
|
||||
it('CONTADO', () => {
|
||||
const [, , p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(2173525, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(243725, 2)
|
||||
})
|
||||
|
||||
it('SEMI CONTADO', () => {
|
||||
const [, , , p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(1198625, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(974900, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(243725, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SENDA — 411-1-7 (L = 2,451,750, apartado 0 en el Excel, N = 4)', () => {
|
||||
const unidad = porCodigo(UNIDADES_SENDA, 'SENDA-411-1-7')
|
||||
const calcular = () =>
|
||||
calcularPropuestas({
|
||||
unidad,
|
||||
acabados: false,
|
||||
apartado: 0, // el Excel de SENDA traía MONTO APARTADO (C15) en 0
|
||||
descuentos: descuentosDefault('SENDA'),
|
||||
numMeses: 4,
|
||||
})
|
||||
|
||||
it('BANCARIO: enganche 5%, mensualidades 5% ÷ N, pago final 90%', () => {
|
||||
const [p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(122587.5, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(122587.5, 2)
|
||||
expect(p.mensualidad).toBeCloseTo(30646.875, 3)
|
||||
expect(p.pagoFinal).toBeCloseTo(2206575, 2)
|
||||
})
|
||||
|
||||
it('INFONAVIT: sin quirk de lista — mensualidades sobre precio final', () => {
|
||||
const [, p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(61293.75, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(61293.75, 2)
|
||||
expect(p.mensualidad).toBeCloseTo(15323.4375, 4)
|
||||
expect(p.pagoFinal).toBeCloseTo(2329162.5, 2)
|
||||
})
|
||||
|
||||
it('CONTADO (desc default 2%)', () => {
|
||||
const [, , p] = calcular()
|
||||
expect(p.precioFinal).toBeCloseTo(2402715, 2)
|
||||
expect(p.enganche).toBeCloseTo(2162443.5, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(240271.5, 2)
|
||||
})
|
||||
|
||||
it('SEMI CONTADO (desc default 1%, split 50/45/5)', () => {
|
||||
const [, , , p] = calcular()
|
||||
expect(p.precioFinal).toBeCloseTo(2427232.5, 2)
|
||||
expect(p.enganche).toBeCloseTo(1213616.25, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(1092254.625, 3)
|
||||
expect(p.mensualidad).toBeCloseTo(273063.65625, 5)
|
||||
expect(p.pagoFinal).toBeCloseTo(121361.625, 3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SENDA — unidad sin fecha de entrega (411-25-A101)', () => {
|
||||
const unidad = porCodigo(UNIDADES_SENDA, 'SENDA-411-25-A101')
|
||||
|
||||
it('N = null y mensualidades "—" con nota; enganche y pago final sí se calculan', () => {
|
||||
expect(unidad.fechaEntrega).toBeNull()
|
||||
const [bancario] = calcularPropuestas({
|
||||
unidad,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: descuentosDefault('SENDA'),
|
||||
})
|
||||
expect(bancario.numMensualidades).toBeNull()
|
||||
expect(bancario.mensualidad).toBeNull()
|
||||
expect(bancario.mensualidadesTotal).toBeNull()
|
||||
expect(bancario.notaMensualidades).toBe('sin fecha de entrega')
|
||||
expect(bancario.enganche).toBeCloseTo(0.05 * 1948000 - APARTADO_DEFAULT, 2)
|
||||
expect(bancario.pagoFinal).toBeCloseTo(0.9 * 1948000, 2)
|
||||
expect(totalPagado(bancario, APARTADO_DEFAULT)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('SOLANA — casa 6 (L = 2,560,000, apartado 20,000, mesesParaFirma = 1)', () => {
|
||||
const unidad = porCodigo(UNIDADES_SOLANA, 'SOLANA-6')
|
||||
const calcular = () =>
|
||||
calcularPropuestas({
|
||||
unidad,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: descuentosDefault('SOLANA'),
|
||||
})
|
||||
|
||||
it('N viene de mesesParaFirma de la unidad', () => {
|
||||
expect(unidad.mesesParaFirma).toBe(1)
|
||||
expect(resolverNumMensualidades(unidad, DESARROLLOS['SOLANA'])).toBe(1)
|
||||
})
|
||||
|
||||
it('BANCARIO: enganche 5% sobre final', () => {
|
||||
const [p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(108000, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(128000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2304000, 2)
|
||||
})
|
||||
|
||||
it('INFONAVIT: enganche 0% con guarda (no resta el apartado)', () => {
|
||||
const [, p] = calcular()
|
||||
expect(p.enganche).toBe(0)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(128000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2432000, 2)
|
||||
})
|
||||
|
||||
it('CONTADO', () => {
|
||||
const [, , p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(2284000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(256000, 2)
|
||||
})
|
||||
|
||||
it('SEMI CONTADO', () => {
|
||||
const [, , , p] = calcular()
|
||||
expect(p.enganche).toBeCloseTo(1260000, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(1024000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(256000, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SOLANA — casa 9 (L = 2,605,000, mesesParaFirma = 1)', () => {
|
||||
const unidad = porCodigo(UNIDADES_SOLANA, 'SOLANA-9')
|
||||
const calcular = () =>
|
||||
calcularPropuestas({
|
||||
unidad,
|
||||
acabados: false,
|
||||
apartado: APARTADO_DEFAULT,
|
||||
descuentos: descuentosDefault('SOLANA'),
|
||||
})
|
||||
|
||||
it('las 4 propuestas al centavo', () => {
|
||||
const [bancario, infonavit, contado, semi] = calcular()
|
||||
expect(bancario.enganche).toBeCloseTo(110250, 2)
|
||||
expect(bancario.mensualidadesTotal).toBeCloseTo(130250, 2)
|
||||
expect(bancario.pagoFinal).toBeCloseTo(2344500, 2)
|
||||
expect(infonavit.enganche).toBe(0)
|
||||
expect(infonavit.mensualidadesTotal).toBeCloseTo(130250, 2)
|
||||
expect(infonavit.pagoFinal).toBeCloseTo(2474750, 2)
|
||||
expect(contado.enganche).toBeCloseTo(2324500, 2)
|
||||
expect(contado.pagoFinal).toBeCloseTo(260500, 2)
|
||||
expect(semi.enganche).toBeCloseTo(1282500, 2)
|
||||
expect(semi.mensualidadesTotal).toBeCloseTo(1042000, 2)
|
||||
expect(semi.pagoFinal).toBeCloseTo(260500, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('configuración de desarrollos', () => {
|
||||
it('los 5 desarrollos tienen 4 propuestas', () => {
|
||||
for (const id of ['DIAGONAL', 'ATELIER', 'ESTANZA', 'SENDA', 'SOLANA']) {
|
||||
expect(DESARROLLOS[id].propuestas).toHaveLength(4)
|
||||
}
|
||||
})
|
||||
|
||||
it('id desconocido cae a DIAGONAL', () => {
|
||||
expect(obtenerDesarrollo('NO_EXISTE').id).toBe('DIAGONAL')
|
||||
expect(obtenerDesarrollo(undefined).id).toBe('DIAGONAL')
|
||||
})
|
||||
|
||||
it('descuentos default por desarrollo', () => {
|
||||
expect(descuentosDefault('DIAGONAL')).toEqual({
|
||||
creditoBancario: 0,
|
||||
creditoInfonavit: 0,
|
||||
contado: 0.02,
|
||||
semiContado: 0.01,
|
||||
})
|
||||
expect(descuentosDefault('SENDA')).toEqual({
|
||||
creditoBancario: 0,
|
||||
creditoInfonavit: 0,
|
||||
contado: 0.02,
|
||||
semiContado: 0.01,
|
||||
})
|
||||
expect(descuentosDefault('ATELIER')).toEqual({
|
||||
creditoBancario: 0,
|
||||
creditoInfonavit: 0,
|
||||
contado: 0,
|
||||
semiContado: 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -129,7 +129,7 @@ describe('totalPagado', () => {
|
||||
describe('indiceMenorTotal', () => {
|
||||
it('devuelve el índice de la propuesta con menor total pagado', () => {
|
||||
const propuestas = calcular()
|
||||
const totales = propuestas.map((p) => totalPagado(p, APARTADO_DEFAULT))
|
||||
const totales = propuestas.map((p) => totalPagado(p, APARTADO_DEFAULT) ?? 0)
|
||||
// Contado (2% desc) tiene el menor total pagado
|
||||
expect(indiceMenorTotal(totales)).toBe(2)
|
||||
})
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import type { Unidad, TipoPropuesta, DescuentosPropuestas } from '../types'
|
||||
import type {
|
||||
Unidad,
|
||||
TipoPropuesta,
|
||||
DescuentosPropuestas,
|
||||
ParametrosDesarrollo,
|
||||
PropuestaConfig,
|
||||
} from '../types'
|
||||
import { DESARROLLO_DEFAULT, obtenerDesarrollo } from '../data/desarrollos'
|
||||
|
||||
/**
|
||||
* Cálculos replicados fielmente del Excel de producción "COTIZADOR" de
|
||||
* Frasa Desarrollos (ya validado). NO "corregir" las fórmulas: algunas
|
||||
* Cálculos replicados fielmente de los Excel de producción "COTIZADOR" de
|
||||
* Frasa Desarrollos (ya validados). NO "corregir" las fórmulas: algunas
|
||||
* usan precio de lista donde intuitivamente iría precio final; así está
|
||||
* en el Excel y así debe quedar.
|
||||
* en los Excel y así debe quedar.
|
||||
*
|
||||
* El motor está parametrizado por desarrollo (ver src/data/desarrollos.ts);
|
||||
* con los parámetros de DIAGONAL reproduce byte a byte las fórmulas
|
||||
* originales de este archivo.
|
||||
*/
|
||||
|
||||
export const APARTADO_DEFAULT = 20000
|
||||
|
||||
// Descuentos por defecto de cada propuesta (fracción, no porcentaje)
|
||||
// Descuentos por defecto de DIAGONAL (fracción, no porcentaje).
|
||||
// Para otros desarrollos usar descuentosDefault(desarrollo) de data/desarrollos.
|
||||
export const DESCUENTOS_DEFAULT: DescuentosPropuestas = {
|
||||
creditoBancario: 0,
|
||||
creditoInfonavit: 0,
|
||||
@@ -19,22 +31,28 @@ export const DESCUENTOS_DEFAULT: DescuentosPropuestas = {
|
||||
|
||||
export interface PropuestaResultado {
|
||||
tipo: TipoPropuesta
|
||||
/** Título para mostrar, viene de la config del desarrollo (p. ej. 'Crédito Cofinavit') */
|
||||
titulo: string
|
||||
descuento: number // fracción (0.02 = 2%)
|
||||
montoDescuento: number
|
||||
precioFinal: number // precio lista - descuento
|
||||
enganche: number // NETO del apartado (el apartado ya se pagó)
|
||||
numMensualidades: number
|
||||
mensualidad: number | null // null => N/A (Contado)
|
||||
mensualidadesTotal: number | null // null => N/A (Contado)
|
||||
numMensualidades: number | null // null => sin fecha de entrega / plazo desconocido
|
||||
mensualidad: number | null // null => N/A (Contado) o plazo desconocido
|
||||
mensualidadesTotal: number | null // null => N/A (Contado) o plazo desconocido
|
||||
pagoFinal: number
|
||||
/** Nota explicativa cuando no se pueden calcular mensualidades ('sin fecha de entrega') */
|
||||
notaMensualidades?: string
|
||||
}
|
||||
|
||||
export interface CotizacionInput {
|
||||
unidad: Unidad
|
||||
acabados: boolean // true => incluye cocina y closet
|
||||
acabados: boolean // true => incluye cocina y closet (ignorado en desarrollos de precio único)
|
||||
apartado: number
|
||||
descuentos: DescuentosPropuestas
|
||||
/** Permite forzar N en tests; por defecto se calcula desde fechaEntrega */
|
||||
/** Desarrollo cuyos parámetros aplican; por defecto unidad.desarrollo ?? 'DIAGONAL' */
|
||||
desarrollo?: string
|
||||
/** Permite forzar N en tests; por defecto se resuelve según la fuenteDeN del desarrollo */
|
||||
numMeses?: number
|
||||
}
|
||||
|
||||
@@ -46,6 +64,26 @@ export function calcularNumMensualidades(fechaEntrega: string, hoy: Date = new D
|
||||
return Math.max(1, Math.round((entrega.getTime() - hoy.getTime()) / MS_POR_MES))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resuelve N según la fuente del desarrollo:
|
||||
* - fijo: nFijo (entrega inmediata)
|
||||
* - porUnidad: mesesParaFirma del inventario (null si falta)
|
||||
* - fechaEntrega: meses desde hoy (null si la unidad no tiene fecha)
|
||||
*/
|
||||
export function resolverNumMensualidades(
|
||||
unidad: Unidad,
|
||||
params: ParametrosDesarrollo,
|
||||
): number | null {
|
||||
switch (params.fuenteDeN) {
|
||||
case 'fijo':
|
||||
return params.nFijo ?? 1
|
||||
case 'porUnidad':
|
||||
return unidad.mesesParaFirma ?? null
|
||||
case 'fechaEntrega':
|
||||
return unidad.fechaEntrega ? calcularNumMensualidades(unidad.fechaEntrega) : null
|
||||
}
|
||||
}
|
||||
|
||||
function baseComun(precioLista: number, descuento: number) {
|
||||
// Excel COTIZADOR: MONTO DE DESCUENTO y PRECIO FINAL C/DESC
|
||||
const montoDescuento = precioLista * descuento
|
||||
@@ -53,102 +91,82 @@ function baseComun(precioLista: number, descuento: number) {
|
||||
return { montoDescuento, precioFinal }
|
||||
}
|
||||
|
||||
/** PROPUESTA 1 — CRÉDITO BANCARIO */
|
||||
function calcularBancario(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
/** Calcula una propuesta comercial con los parámetros dados */
|
||||
function calcularPropuestaConfig(
|
||||
cfg: PropuestaConfig,
|
||||
precioLista: number,
|
||||
apartado: number,
|
||||
descuento: number,
|
||||
n: number | null,
|
||||
): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M17 — enganche 2.5% sobre precio LISTA, neto del apartado
|
||||
const enganche = 0.025 * precioLista - apartado
|
||||
// Excel COTIZADOR!M19 — pago final 90% del precio final c/desc
|
||||
const pagoFinal = 0.9 * precioFinal
|
||||
// Excel COTIZADOR!M18 — remanente a mensualidades
|
||||
const mensualidadesTotal = precioFinal - apartado - enganche - pagoFinal
|
||||
|
||||
// Enganche: pct sobre su base, neto del apartado (salvo la guarda de SOLANA)
|
||||
const baseEnganche = cfg.engancheBase === 'lista' ? precioLista : precioFinal
|
||||
const enganche =
|
||||
cfg.engancheCeroSinApartado && cfg.pctEnganche === 0
|
||||
? 0
|
||||
: cfg.pctEnganche * baseEnganche - apartado
|
||||
|
||||
const basePagoFinal = cfg.pagoFinalBase === 'lista' ? precioLista : precioFinal
|
||||
const pagoFinal = cfg.pctPagoFinal * basePagoFinal
|
||||
|
||||
let mensualidadesTotal: number | null = null
|
||||
let mensualidad: number | null = null
|
||||
let notaMensualidades: string | undefined
|
||||
if (cfg.modoMensualidades !== 'ninguna') {
|
||||
if (n === null) {
|
||||
// Unidad sin fecha de entrega / plazo: el Excel mostraría #N/A
|
||||
notaMensualidades = 'sin fecha de entrega'
|
||||
} else {
|
||||
switch (cfg.modoMensualidades) {
|
||||
case 'remanente':
|
||||
mensualidadesTotal = precioFinal - apartado - enganche - pagoFinal
|
||||
break
|
||||
case 'remanenteLista':
|
||||
// Quirk DIAGONAL Infonavit: el Excel usa precioLista aquí, NO precioFinal
|
||||
mensualidadesTotal = precioLista - pagoFinal - apartado - enganche
|
||||
break
|
||||
case 'porcentaje':
|
||||
mensualidadesTotal = precioFinal * (1 - cfg.pctEnganche - cfg.pctPagoFinal)
|
||||
break
|
||||
}
|
||||
mensualidad = mensualidadesTotal / n
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tipo: 'CREDITO_BANCARIO',
|
||||
tipo: cfg.tipo,
|
||||
titulo: cfg.titulo,
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: mensualidadesTotal / n,
|
||||
mensualidad,
|
||||
mensualidadesTotal,
|
||||
pagoFinal,
|
||||
...(notaMensualidades !== undefined ? { notaMensualidades } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
/** PROPUESTA 2 — CRÉDITO INFONAVIT-COFINAVIT */
|
||||
function calcularInfonavit(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M26 — enganche 2.5% sobre precio LISTA, neto del apartado
|
||||
const enganche = 0.025 * precioLista - apartado
|
||||
// Excel COTIZADOR!M28 — pago final 95% del precio final c/desc
|
||||
const pagoFinal = 0.95 * precioFinal
|
||||
// Excel COTIZADOR!M27 — OJO: el Excel usa precioLista aquí, NO precioFinal. Replicar tal cual.
|
||||
const mensualidadesTotal = precioLista - pagoFinal - apartado - enganche
|
||||
return {
|
||||
tipo: 'CREDITO_INFONAVIT_COFINAVIT',
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: mensualidadesTotal / n,
|
||||
mensualidadesTotal,
|
||||
pagoFinal,
|
||||
}
|
||||
}
|
||||
|
||||
/** PROPUESTA 3 — CONTADO (sin mensualidades) */
|
||||
function calcularContado(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M36 — enganche 90% del precio final c/desc, neto del apartado
|
||||
const enganche = 0.9 * precioFinal - apartado
|
||||
// Excel COTIZADOR!M37 — pago final 10% del precio final c/desc
|
||||
const pagoFinal = 0.1 * precioFinal
|
||||
return {
|
||||
tipo: 'CONTADO',
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: null, // N/A
|
||||
mensualidadesTotal: null, // N/A
|
||||
pagoFinal,
|
||||
}
|
||||
}
|
||||
|
||||
/** PROPUESTA 4 — SEMI CONTADO */
|
||||
function calcularSemiContado(precioLista: number, apartado: number, descuento: number, n: number): PropuestaResultado {
|
||||
const { montoDescuento, precioFinal } = baseComun(precioLista, descuento)
|
||||
// Excel COTIZADOR!M45 — enganche 50% del precio final c/desc, neto del apartado
|
||||
const enganche = 0.5 * precioFinal - apartado
|
||||
// Excel COTIZADOR!M46 — 40% del precio final a mensualidades (= 1 - 0.5 - 0.1)
|
||||
const mensualidadesTotal = precioFinal * 0.4
|
||||
// Excel COTIZADOR!M47 — pago final 10% del precio final c/desc
|
||||
const pagoFinal = 0.1 * precioFinal
|
||||
return {
|
||||
tipo: 'SEMI_CONTADO',
|
||||
descuento,
|
||||
montoDescuento,
|
||||
precioFinal,
|
||||
enganche,
|
||||
numMensualidades: n,
|
||||
mensualidad: mensualidadesTotal / n,
|
||||
mensualidadesTotal,
|
||||
pagoFinal,
|
||||
}
|
||||
}
|
||||
|
||||
/** Precio de lista según si incluye cocina y closet (acabados) */
|
||||
/**
|
||||
* Precio de lista según si incluye cocina y closet (acabados).
|
||||
* En desarrollos de precio único la unidad trae `precioLista` y no hay
|
||||
* distinción de acabados.
|
||||
*/
|
||||
export function obtenerPrecioLista(unidad: Unidad, acabados: boolean): number {
|
||||
if (unidad.precioLista != null) return unidad.precioLista
|
||||
return acabados ? unidad.precioConAcabados : unidad.precioSinAcabados
|
||||
}
|
||||
|
||||
/**
|
||||
* Total pagado por el cliente en una propuesta:
|
||||
* apartado + enganche (neto del apartado) + mensualidades + pago final.
|
||||
* Devuelve null cuando el total no se puede conocer (plazo desconocido).
|
||||
*/
|
||||
export function totalPagado(p: PropuestaResultado, apartado: number): number {
|
||||
export function totalPagado(p: PropuestaResultado, apartado: number): number | null {
|
||||
if (p.notaMensualidades) return null
|
||||
return apartado + p.enganche + (p.mensualidadesTotal ?? 0) + p.pagoFinal
|
||||
}
|
||||
|
||||
@@ -162,15 +180,13 @@ export function indiceMenorTotal(totales: number[]): number {
|
||||
return indice
|
||||
}
|
||||
|
||||
/** Calcula las 4 propuestas comerciales */
|
||||
/** Calcula las propuestas comerciales del desarrollo de la unidad */
|
||||
export function calcularPropuestas(input: CotizacionInput): PropuestaResultado[] {
|
||||
const { unidad, acabados, apartado, descuentos } = input
|
||||
const params = obtenerDesarrollo(input.desarrollo ?? unidad.desarrollo ?? DESARROLLO_DEFAULT)
|
||||
const precioLista = obtenerPrecioLista(unidad, acabados)
|
||||
const n = input.numMeses ?? calcularNumMensualidades(unidad.fechaEntrega)
|
||||
return [
|
||||
calcularBancario(precioLista, apartado, descuentos.creditoBancario, n),
|
||||
calcularInfonavit(precioLista, apartado, descuentos.creditoInfonavit, n),
|
||||
calcularContado(precioLista, apartado, descuentos.contado, n),
|
||||
calcularSemiContado(precioLista, apartado, descuentos.semiContado, n),
|
||||
]
|
||||
const n = input.numMeses ?? resolverNumMensualidades(unidad, params)
|
||||
return params.propuestas.map((cfg) =>
|
||||
calcularPropuestaConfig(cfg, precioLista, apartado, descuentos[cfg.claveDescuento], n),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { jsPDF } from 'jspdf'
|
||||
import autoTable from 'jspdf-autotable'
|
||||
import type { Unidad, Cliente } from '../types'
|
||||
import type { Unidad, Cliente, ParametrosDesarrollo } from '../types'
|
||||
import type { PropuestaResultado } from './calculations'
|
||||
import { formatoMoneda, formatoPorcentaje, formatoFechaLarga, fechaISOHoy } from './formatters'
|
||||
import { DESARROLLOS, DESARROLLO_DEFAULT } from '../data/desarrollos'
|
||||
|
||||
export interface PdfInput {
|
||||
unidad: Unidad
|
||||
@@ -11,14 +12,11 @@ export interface PdfInput {
|
||||
precioLista: number
|
||||
propuestas: PropuestaResultado[]
|
||||
cliente: Cliente
|
||||
/** Parámetros del desarrollo; por defecto DIAGONAL */
|
||||
desarrollo?: ParametrosDesarrollo
|
||||
}
|
||||
|
||||
const TITULOS: Record<PropuestaResultado['tipo'], string> = {
|
||||
CREDITO_BANCARIO: 'CREDITO BANCARIO',
|
||||
CREDITO_INFONAVIT_COFINAVIT: 'CREDITO INFONAVIT-COFINAVIT',
|
||||
CONTADO: 'CONTADO',
|
||||
SEMI_CONTADO: 'SEMI CONTADO',
|
||||
}
|
||||
const formatoM2Pdf = (v: number | null): string => (v === null ? '—' : `${v} m2`)
|
||||
|
||||
const DISCLAIMER =
|
||||
'ESTA COTIZACION NO INCLUYE GASTOS DE ESCRITURACION. LOS VALORES ANTERIORES SON EN PESOS, MERAMENTE INFORMATIVOS, POR LO QUE ESTE DOCUMENTO NO CONSTITUYE PREAPROBACIÓN Y POR LO TANTO NO COMPROMETE AL DESARROLLADOR.\n\nMONTOS, PORCENTAJES Y PRECIOS EN LA PRESENTE COTIZACIÓN PUEDEN CAMBIAR SIN PREVIO AVISO.\n\nEL MONTO TOTAL DE ENGANCHE INCLUYE LA DEDUCCIÓN DEL MONTO DE APARTADO.\n\nLA PRESENTE COTIZACIÓN TIENE UNA VIGENCIA DE 3 DÍAS NATURALES A PARTIR DE LA FECHA DE EMISIÓN, PARA CONGELAR EL PRECIO DE VENTA ES NECESARIO REALIZAR EL PAGO DE SU APARTADO COMPLETO Y TENDRÁ 7 DÍAS PARA FORMALIZAR Y NO PERDER LA UBICACIÓN.'
|
||||
@@ -41,7 +39,8 @@ export interface DocumentoPdf {
|
||||
}
|
||||
|
||||
/** Construye el documento PDF "ANEXO A" (sin descargarlo) */
|
||||
export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, propuestas, cliente }: PdfInput): DocumentoPdf {
|
||||
export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, propuestas, cliente, desarrollo }: PdfInput): DocumentoPdf {
|
||||
const params = desarrollo ?? DESARROLLOS[DESARROLLO_DEFAULT]
|
||||
const doc = new jsPDF()
|
||||
const hoy = new Date()
|
||||
const limiteDocumentos = new Date(hoy.getTime() + 7 * 86400000)
|
||||
@@ -69,6 +68,9 @@ export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, pro
|
||||
doc.setFontSize(10)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
doc.text(formatoFechaLarga(hoy), 105, y, { align: 'center' })
|
||||
y += 5
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`DESARROLLO ${params.nombre.toUpperCase()}`, 105, y, { align: 'center' })
|
||||
y += 8
|
||||
|
||||
// 2. Tabla de unidad
|
||||
@@ -82,16 +84,16 @@ export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, pro
|
||||
'MONTO APARTADO $ MN', 'CUOTA MANTENIMIENTO',
|
||||
]],
|
||||
body: [[
|
||||
`CASA ${unidad.codigo}`,
|
||||
`${params.prefijoUnidadPdf} ${unidad.codigo}`,
|
||||
unidad.modelo,
|
||||
acabados ? 'SI' : 'NO',
|
||||
params.precioUnico ? 'N/A' : acabados ? 'SI' : 'NO',
|
||||
formatoMoneda(precioLista),
|
||||
unidad.status,
|
||||
`${unidad.supConstruccion} m2`,
|
||||
`${unidad.supExcedente} m2`,
|
||||
`${unidad.supEstacionamiento} m2`,
|
||||
formatoM2Pdf(unidad.supConstruccion),
|
||||
formatoM2Pdf(unidad.supExcedente),
|
||||
formatoM2Pdf(unidad.supEstacionamiento),
|
||||
formatoMoneda(apartado),
|
||||
'1,500 Aprox.',
|
||||
params.cuotaMantenimiento,
|
||||
]],
|
||||
})
|
||||
y = finalY(doc) + 8
|
||||
@@ -103,8 +105,9 @@ export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, pro
|
||||
y += 4
|
||||
|
||||
propuestas.forEach((p, i) => {
|
||||
const mensualidades =
|
||||
p.mensualidad !== null && p.mensualidadesTotal !== null
|
||||
const mensualidades = p.notaMensualidades
|
||||
? { pagos: `— (${p.notaMensualidades})`, total: '—' }
|
||||
: p.mensualidad !== null && p.mensualidadesTotal !== null
|
||||
? {
|
||||
pagos: `${p.numMensualidades} × ${formatoMoneda(p.mensualidad)}`,
|
||||
total: formatoMoneda(p.mensualidadesTotal),
|
||||
@@ -116,7 +119,7 @@ export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, pro
|
||||
margin: { left: MARGEN, right: MARGEN },
|
||||
styles: { fontSize: 8 },
|
||||
headStyles: { fillColor: [4, 120, 87] },
|
||||
head: [[`PROPUESTA COMERCIAL #${i + 1} — ${TITULOS[p.tipo]}`, '%', 'PAGOS MENSUALES', 'TOTAL']],
|
||||
head: [[`PROPUESTA COMERCIAL #${i + 1} — ${p.titulo.toUpperCase()}`, '%', 'PAGOS MENSUALES', 'TOTAL']],
|
||||
body: [
|
||||
['DESCUENTO', formatoPorcentaje(p.descuento), '', ''],
|
||||
['MONTO DE DESCUENTO', '', '', formatoMoneda(p.montoDescuento)],
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ComparadorPropuestas } from '../components/ComparadorPropuestas'
|
||||
import { generarPdf } from '../lib/pdf'
|
||||
import { formatoMoneda, formatoFechaLarga } from '../lib/formatters'
|
||||
import { calcularVigencia } from '../lib/vigencia'
|
||||
import { LISTA_DESARROLLOS, obtenerDesarrollo } from '../data/desarrollos'
|
||||
import type { Cliente } from '../types'
|
||||
|
||||
const esquemaCliente = z.object({
|
||||
@@ -22,13 +23,6 @@ const esquemaCliente = z.object({
|
||||
|
||||
type FormularioCliente = z.infer<typeof esquemaCliente>
|
||||
|
||||
const TITULOS_PROPUESTAS: [string, keyof import('../types').DescuentosPropuestas][] = [
|
||||
['Crédito Bancario', 'creditoBancario'],
|
||||
['Crédito Infonavit-Cofinavit', 'creditoInfonavit'],
|
||||
['Contado', 'contado'],
|
||||
['Semi Contado', 'semiContado'],
|
||||
]
|
||||
|
||||
const campoInputBase =
|
||||
'w-full border bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none'
|
||||
const campoInput = `${campoInputBase} border-neutral-300`
|
||||
@@ -40,6 +34,9 @@ export default function CotizadorPage() {
|
||||
const { unidades, cargando, error, usandoSeed } = useUnidades()
|
||||
const cotizacion = useCotizacion()
|
||||
const {
|
||||
desarrollo,
|
||||
setDesarrollo,
|
||||
params,
|
||||
unidad,
|
||||
setUnidad,
|
||||
acabados,
|
||||
@@ -52,6 +49,9 @@ export default function CotizadorPage() {
|
||||
propuestas,
|
||||
} = cotizacion
|
||||
|
||||
const unidadesDesarrollo = unidades.filter((u) => (u.desarrollo ?? 'DIAGONAL') === desarrollo)
|
||||
const disponiblesDesarrollo = unidadesDesarrollo.filter((u) => u.status === 'DISPONIBLE').length
|
||||
|
||||
const {
|
||||
register,
|
||||
watch,
|
||||
@@ -72,7 +72,7 @@ export default function CotizadorPage() {
|
||||
email: watch('email') || undefined,
|
||||
telefono: watch('telefono') || undefined,
|
||||
}
|
||||
generarPdf({ unidad, acabados, apartado, precioLista, propuestas, cliente })
|
||||
generarPdf({ unidad, acabados, apartado, precioLista, propuestas, cliente, desarrollo: params })
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -105,15 +105,51 @@ export default function CotizadorPage() {
|
||||
{cargando ? (
|
||||
<p className="text-sm text-neutral-500">Cargando unidades…</p>
|
||||
) : (
|
||||
<UnitSelector
|
||||
unidades={unidades}
|
||||
valor={unidad?.codigo ?? ''}
|
||||
onChange={(codigo) =>
|
||||
setUnidad(unidades.find((u) => u.codigo === codigo) ?? null)
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<div>
|
||||
<span className={etiquetaCampo}>Desarrollo</span>
|
||||
<div className="flex flex-wrap border border-neutral-300" role="group">
|
||||
{LISTA_DESARROLLOS.map((id) => {
|
||||
const activo = id === desarrollo
|
||||
const total = unidades.filter((u) => (u.desarrollo ?? 'DIAGONAL') === id).length
|
||||
const disponibles = unidades.filter(
|
||||
(u) => (u.desarrollo ?? 'DIAGONAL') === id && u.status === 'DISPONIBLE',
|
||||
).length
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
aria-pressed={activo}
|
||||
onClick={() => setDesarrollo(id)}
|
||||
className={`px-4 py-2.5 text-xs font-bold tracking-widest uppercase transition-colors duration-150 ${
|
||||
activo
|
||||
? 'bg-accent text-white'
|
||||
: 'bg-white text-neutral-600 hover:bg-neutral-100'
|
||||
}`}
|
||||
>
|
||||
{obtenerDesarrollo(id).nombre}
|
||||
<span className={`ml-1 font-normal ${activo ? 'text-white' : 'text-neutral-400'}`}>
|
||||
{disponibles}/{total}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-neutral-500">
|
||||
{unidadesDesarrollo.length} unidades · {disponiblesDesarrollo} disponibles
|
||||
</p>
|
||||
</div>
|
||||
<UnitSelector
|
||||
unidades={unidadesDesarrollo}
|
||||
valor={unidad?.codigo ?? ''}
|
||||
onChange={(codigo) =>
|
||||
setUnidad(unidadesDesarrollo.find((u) => u.codigo === codigo) ?? null)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!params.precioUnico && (
|
||||
<div>
|
||||
<span className={etiquetaCampo}>Cocina y closet</span>
|
||||
<div className="inline-flex border border-neutral-300" role="group">
|
||||
@@ -137,6 +173,7 @@ export default function CotizadorPage() {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="apartado" className={etiquetaCampo}>
|
||||
@@ -227,9 +264,9 @@ export default function CotizadorPage() {
|
||||
<ProposalCard
|
||||
key={p.tipo}
|
||||
numero={i + 1}
|
||||
titulo={TITULOS_PROPUESTAS[i][0]}
|
||||
titulo={p.titulo}
|
||||
propuesta={p}
|
||||
onDescuentoChange={(v) => setDescuento(TITULOS_PROPUESTAS[i][1], v)}
|
||||
onDescuentoChange={(v) => setDescuento(params.propuestas[i].claveDescuento, v)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -237,7 +274,7 @@ export default function CotizadorPage() {
|
||||
precioLista={precioLista}
|
||||
apartado={apartado}
|
||||
propuestas={propuestas}
|
||||
titulos={TITULOS_PROPUESTAS.map(([titulo]) => titulo)}
|
||||
titulos={propuestas.map((p) => p.titulo)}
|
||||
/>
|
||||
<div className="mt-16 flex justify-end">
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { getUnidades } from './api'
|
||||
import { UNIDADES_SEED } from '../data/unidades'
|
||||
import { INVENTARIO_SEED } from '../data/unidades'
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
const unidadApi: Unidad = {
|
||||
@@ -50,7 +50,7 @@ describe('getUnidades', () => {
|
||||
const resultado = await getUnidades()
|
||||
|
||||
expect(resultado.usandoSeed).toBe(true)
|
||||
expect(resultado.unidades).toEqual(UNIDADES_SEED)
|
||||
expect(resultado.unidades).toEqual(INVENTARIO_SEED)
|
||||
expect(warn).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('getUnidades', () => {
|
||||
const resultado = await getUnidades()
|
||||
|
||||
expect(resultado.usandoSeed).toBe(true)
|
||||
expect(resultado.unidades).toEqual(UNIDADES_SEED)
|
||||
expect(resultado.unidades).toEqual(INVENTARIO_SEED)
|
||||
expect(warn).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Unidad } from '../types'
|
||||
import { UNIDADES_SEED } from '../data/unidades'
|
||||
import { INVENTARIO_SEED } from '../data/unidades'
|
||||
|
||||
// TODO produccion: '/api' same-origin detras de NGINX
|
||||
const API_URL = import.meta.env.VITE_API_URL ?? 'http://192.168.10.123:3000/api'
|
||||
@@ -23,6 +23,6 @@ export async function getUnidades(): Promise<GetUnidadesResult> {
|
||||
return { unidades, usandoSeed: false }
|
||||
} catch (err) {
|
||||
console.warn('[api] No se pudo obtener el inventario del API; usando seed local.', err)
|
||||
return { unidades: UNIDADES_SEED, usandoSeed: true }
|
||||
return { unidades: INVENTARIO_SEED, usandoSeed: true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
export type StatusUnidad =
|
||||
| 'DISPONIBLE'
|
||||
| 'APARTADA'
|
||||
| 'VENDIDA'
|
||||
| 'APARTADO'
|
||||
| 'VENDIDO'
|
||||
| 'ESCRITURADO'
|
||||
| 'BLOQUEADO'
|
||||
|
||||
export interface Unidad {
|
||||
codigo: string // 'U-101'
|
||||
modelo: string // '1R' | '2RA' | '2RB' | '2RC'
|
||||
fechaEntrega: string // ISO 'YYYY-MM-DD' (terminación de obra)
|
||||
supEstacionamiento: number // m2
|
||||
supConstruccion: number // m2
|
||||
supExcedente: number // m2
|
||||
precioSinAcabados: number // MXN
|
||||
precioConAcabados: number // MXN
|
||||
status: 'DISPONIBLE' | 'APARTADA' | 'VENDIDA'
|
||||
desarrollo?: string // lo agrega el API Gateway (Monday); el seed local no lo trae
|
||||
codigo: string // 'U-101' (DIAGONAL), 'ATELIER-10', 'ESTANZA-52', 'SENDA-411-1-7', 'SOLANA-6'
|
||||
modelo: string // '1R' | '2RA' | '2RB' | '2RC' | 'TRIBECA' | 'SOHO' | 'DUPLO A' | ...
|
||||
fechaEntrega: string | null // ISO 'YYYY-MM-DD' (terminación de obra); null si no aplica o se desconoce
|
||||
supEstacionamiento: number | null // m2
|
||||
supConstruccion: number | null // m2
|
||||
supExcedente: number | null // m2
|
||||
precioSinAcabados: number // MXN (en desarrollos de precio único = precioLista)
|
||||
precioConAcabados: number // MXN (en desarrollos de precio único = precioLista)
|
||||
status: StatusUnidad
|
||||
desarrollo?: string // 'DIAGONAL' | 'ATELIER' | 'ESTANZA' | 'SENDA' | 'SOLANA'
|
||||
/** Precio único de lista (desarrollos sin distinción de acabados) */
|
||||
precioLista?: number | null
|
||||
/** Plazo de mensualidades cuando lo fija el inventario (SOLANA: meses para firma) */
|
||||
mesesParaFirma?: number | null
|
||||
/** Datos extra del inventario: prototipo, manzana, lote, etapa, cajones, etc. */
|
||||
extras?: Record<string, string | number | null>
|
||||
}
|
||||
|
||||
export interface Cliente {
|
||||
@@ -29,3 +44,49 @@ export interface DescuentosPropuestas {
|
||||
contado: number
|
||||
semiContado: number
|
||||
}
|
||||
|
||||
// ---------- Parametrización por desarrollo ----------
|
||||
|
||||
/** Sobre qué precio se calcula un componente: lista o final (c/descuento) */
|
||||
export type BasePrecio = 'lista' | 'final'
|
||||
|
||||
/**
|
||||
* Cómo se calcula el total de mensualidades:
|
||||
* - ninguna: sin mensualidades (Contado)
|
||||
* - remanente: precioFinal − apartado − enganche − pagoFinal
|
||||
* - remanenteLista: precioLista − pagoFinal − apartado − enganche (quirk DIAGONAL Infonavit)
|
||||
* - porcentaje: precioFinal × (1 − pctEnganche − pctPagoFinal)
|
||||
*/
|
||||
export type ModoMensualidades = 'ninguna' | 'remanente' | 'remanenteLista' | 'porcentaje'
|
||||
|
||||
export interface PropuestaConfig {
|
||||
tipo: TipoPropuesta
|
||||
/** Clave dentro de DescuentosPropuestas donde vive el descuento editable */
|
||||
claveDescuento: keyof DescuentosPropuestas
|
||||
/** Título para mostrar (p. ej. 'Crédito Cofinavit' en ATELIER) */
|
||||
titulo: string
|
||||
descuentoDefault: number // fracción (0.02 = 2%)
|
||||
pctEnganche: number
|
||||
engancheBase: BasePrecio
|
||||
pctPagoFinal: number
|
||||
pagoFinalBase: BasePrecio
|
||||
modoMensualidades: ModoMensualidades
|
||||
/** SOLANA Infonavit: si pctEnganche es 0, el enganche es 0 (no se resta el apartado) */
|
||||
engancheCeroSinApartado?: boolean
|
||||
}
|
||||
|
||||
export interface ParametrosDesarrollo {
|
||||
id: string // 'DIAGONAL'
|
||||
nombre: string // nombre para mostrar
|
||||
/** true => un solo precio de lista; se oculta el toggle de acabados */
|
||||
precioUnico: boolean
|
||||
/** De dónde sale N (número de mensualidades) */
|
||||
fuenteDeN: 'fechaEntrega' | 'fijo' | 'porUnidad'
|
||||
/** N fijo cuando fuenteDeN === 'fijo' (entrega inmediata / term. obra) */
|
||||
nFijo?: number
|
||||
/** Texto de cuota de mantenimiento para el PDF */
|
||||
cuotaMantenimiento: string
|
||||
/** Prefijo de la unidad en el PDF ('CASA', 'DEPTO') */
|
||||
prefijoUnidadPdf: string
|
||||
propuestas: PropuestaConfig[]
|
||||
}
|
||||
|
||||
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