UNPKG

@horizon-modules/property-model-v3

Version:

Modelo de propriedades imobiliárias v3 - Sistema de atributos dinâmicos

1 lines 122 kB
{"version":3,"sources":["../src/models/property-type.schema.ts","../src/models/property-attributes-model.ts","../src/models/property-attributes-fields-list-model.ts","../src/utils/index.ts","../src/services/fake-data-generator/index.ts","../src/services/profiler/index.ts","../src/services/api-client/index.ts","../src/services/property-customizer/index.ts","../src/data/json-schema/property-model.json","../src/models/property-type.schema.json"],"sourcesContent":["import { z } from \"zod\"\n\n// Schemas para Media Types\nexport const ImageMediaSchema = z.object({\n full: z.string(),\n md: z.string().optional(),\n sm: z.string().optional(),\n cover: z.boolean().optional(),\n})\n\nexport const VideoMediaSchema = z.object({\n provider: z.string().optional(),\n id: z.string().optional(),\n embed_url: z.string().optional(),\n})\n\nexport const DocumentMediaSchema = z.object({\n name: z.string(),\n url: z.string(),\n})\n\nexport const VirtualTourMediaSchema = z.object({\n embed_url: z.string(),\n})\n\n// Schema para MediaAssets\nexport const MediaAssetsSchema = z.object({\n images: z.array(ImageMediaSchema).optional(),\n videos: z.array(VideoMediaSchema).optional(),\n virtual_tours: z.array(VirtualTourMediaSchema).optional(),\n documents: z.array(DocumentMediaSchema).optional(),\n})\n\n// Schema para Settings\nexport const SettingsFormatSchema = z.object({\n currency_unit: z.union([z.literal(\"BRL\"), z.literal(\"USD\")]).optional(),\n area_unit: z.union([z.literal(\"m2\"), z.literal(\"ft2\")]).optional(),\n distance_unit: z.union([z.literal(\"km\"), z.literal(\"mi\"), z.literal(\"meters\")]).optional(),\n exibir_no_mapa: z.boolean().optional(),\n})\n\n// Schema para SEO\nexport const SEOSchema = z.object({\n meta_title: z.string().optional(),\n meta_description: z.string().optional(),\n})\n\n// Schema principal para PropertyModel\nexport const PropertyModelSchema = z.object({\n reference: z.string(),\n title: z.string(),\n description: z.string(),\n media_assets: MediaAssetsSchema,\n attributes: z.record(z.string(), z.any()),\n settings: SettingsFormatSchema.optional(),\n seo: SEOSchema.optional(),\n updated_at: z.string().optional(),\n})\n\n// Tipos inferidos a partir dos schemas\nexport type ImageMedia = z.infer<typeof ImageMediaSchema>\nexport type VideoMedia = z.infer<typeof VideoMediaSchema>\nexport type DocumentMedia = z.infer<typeof DocumentMediaSchema>\nexport type VirtualTourMedia = z.infer<typeof VirtualTourMediaSchema>\nexport type MediaAssets = z.infer<typeof MediaAssetsSchema>\nexport type SettingsFormat = z.infer<typeof SettingsFormatSchema>\nexport type SEO = z.infer<typeof SEOSchema>\nexport type PropertyModel = z.infer<typeof PropertyModelSchema>\n\n// Função helper para validação\nexport const validatePropertyModel = (data: unknown): PropertyModel => {\n return PropertyModelSchema.parse(data)\n}\n\n// Função helper para validação safe (retorna resultado com sucesso/erro)\nexport const safeValidatePropertyModel = (data: unknown) => {\n return PropertyModelSchema.safeParse(data)\n}\n\n// Tipos legados para compatibilidade\nexport const AttributeSchema = z.object({\n key: z.string(),\n value: z.any(),\n valueLabel: z.union([z.string(), z.array(z.string())]).optional(),\n composedLabel: z.string().optional(),\n type: z.string().optional(),\n unit: z.string().optional(),\n format: z.string().optional(),\n})\n\nexport type Attribute = z.infer<typeof AttributeSchema>\nexport type PropertyType = PropertyModel // Alias para compatibilidade\nexport const PropertyTypeSchema = PropertyModelSchema // Alias para compatibilidade\nexport const validatePropertyType = validatePropertyModel // Alias para compatibilidade\nexport const safeValidatePropertyType = safeValidatePropertyModel // Alias para compatibilidade","/* prettier-ignore */\n\nexport const PropertyAttributesModel = [\n // Comercial\n { key: \"operacao\", \t\t\t\t\t\t\t label: \"Operação\", type: \"String[]\", cat: \"comercial\", order: 1, },\n { key: \"etapa_lancamento\", label: \"Etapa do lançamento\", type: \"String[]\", cat: \"comercial\", order: 2, },\n { key: \"status_comercial\", label: \"Status comercial\", type: \"String[]\", cat: \"comercial\", order: 3, },\n { key: \"destaque\", label: \"Imóvel em destaque\", type: \"Boolean\", cat: \"comercial\", order: 4, },\n { key: \"exclusividade\", label: \"Exclusividade do imóvel\", type: \"Boolean\", cat: \"comercial\", order: 5, },\n { key: \"financiavel\", label: \"Financiável\", type: \"Boolean\", cat: \"comercial\", order: 6, },\n { key: \"seguro_fianca\", label: \"Seguro fiança\", type: \"Boolean\", cat: \"comercial\", order: 7, },\n { key: \"aceita_permuta\", label: \"Aceita permuta\", type: \"Boolean\", cat: \"comercial\", order: 8, },\n { key: \"aceita_parcelamento\", label: \"Aceita parcelamento\", type: \"Boolean\", cat: \"comercial\", order: 9, },\n\n // Valores\n { key: \"valor_condominio\", label: \"Valor do condomínio\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 20, },\n { key: \"valor_total_venda\", label: \"Valor total de venda\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", composedLabel: \"{{valueLabel}}\", order: 21, },\n { key: \"valor_total_mensal\", label: \"Valor total de locação\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", composedLabel: \"{{valueLabel}} /mês\", order: 22, },\n { key: \"valor_venda\", label: \"Valor de venda\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", composedLabel: \"{{valueLabel}}\", order: 23, },\n { key: \"valor_locacao\", label: \"Valor de locação mensal\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", composedLabel: \"{{valueLabel}} /mês\", order: 24, },\n { key: \"valor_diaria\", label: \"Valor da diária\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", composedLabel: \"{{valueLabel}} /dia\", order: 25, },\n { key: \"valor_pacote_dias\", label: \"Valor do pacote de dias\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 26, },\n { key: \"valor_iptu\", label: \"Valor do IPTU\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 27, },\n { key: \"valor_fci\", label: \"Valor do Fundo de conservação\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 28, },\n { key: \"valor_seguro_incendio\", label: \"Valor do seguro incêndio\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 29, },\n { key: \"valor_m2\", label: \"Valor do m²\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 30, },\n { key: \"valor_taxa_limpeza\", label: \"Valor da taxa de limpeza\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 31, },\n { key: \"valor_taxa_esgoto\", label: \"Valor da taxa de esgoto\", type: \"Number\", role:\"currency\", unit: \"BRL\", cat: \"valores\", order: 32, },\n\n // Situações\n { key: \"alugado\", label: \"Alugado\", type: \"Boolean\", cat: \"situacoes\", order: 40, },\n { key: \"vendido\", label: \"Vendido\", type: \"Boolean\", cat: \"situacoes\", order: 41, },\n { key: \"reservado\", label: \"Reservado\", type: \"Boolean\", cat: \"situacoes\", order: 42, },\n { key: \"situacao_ocupacao\", label: \"Situação de ocupação\", type: \"String\", cat: \"situacoes\", order: 43, },\n { key: \"financiado\", label: \"Financiado\", type: \"Boolean\", cat: \"situacoes\", order: 44, },\n\n // Legal / Documental\n { key: \"averbado\", label: \"Averbado\", type: \"Boolean\", cat: \"legal\", order: 60, },\n { key: \"escriturado\", label: \"Escriturado\", type: \"Boolean\", cat: \"legal\", order: 61, },\n { key: \"incorporado\", label: \"Incorporado\", type: \"Boolean\", cat: \"legal\", order: 62, },\n\n // Normas / Permissões / Exigências\n { key: \"numero_pessoas\", label: \"Número de pessoas\", type: \"Number\", role:\"count\", cat: \"normas\", order: 81, },\n\n // Localização\n { key: \"endereco_pais\", label: \"País\", type: \"String\", cat: \"localizacao\", order: 101, },\n { key: \"endereco_cep\", label: \"CEP\", type: \"String\", cat: \"localizacao\", order: 102, },\n { key: \"endereco_estado\", label: \"Estado\", type: \"String\", cat: \"localizacao\", order: 103, },\n { key: \"endereco_cidade\", label: \"Cidade\", type: \"String\", cat: \"localizacao\", order: 104, },\n { key: \"endereco_bairro\", label: \"Bairro\", type: \"String\", cat: \"localizacao\", order: 105, },\n { key: \"endereco_logradouro\", label: \"Logradouro\", type: \"String\", cat: \"localizacao\", order: 106, },\n { key: \"endereco_numero\", label: \"Número do imóvel\", type: \"String\", cat: \"localizacao\", order: 107, },\n { key: \"endereco_complemento\", label: \"Complemento\", type: \"String\", cat: \"localizacao\", order: 108, },\n { key: \"endereco_referencia\", label: \"Referência (endereço)\", type: \"String\", cat: \"localizacao\", order: 109, },\n { key: \"latitude\", label: \"Latitude\", type: \"Number\", cat: \"localizacao\", order: 110, },\n { key: \"longitude\", label: \"Longitude\", type: \"Number\", cat: \"localizacao\", order: 111, },\n\n // Condomínio\n { key: \"condominio_nome\", label: \"Nome do condomínio\", type: \"String\", cat: \"condominio\", order: 120, },\n { key: \"condominio_tipo\", label: \"Tipo do condomínio\", type: \"String\", cat: \"condominio\", order: 121, },\n { key: \"condominio_fechado\", label: \"Condomínio fechado\", type: \"Boolean\", cat: \"condominio\", order: 122, },\n\n // Localidade\n { key: \"distancia_mar\", label: \"Distância do mar\", type: \"Number\", role:\"distance\", unit: \"m\", cat: \"localidade\", order: 141, },\n { key: \"imovel_no_litoral\", label: \"Imóvel no litoral\", type: \"Boolean\", cat: \"localidade\", order: 142, },\n { key: \"frente_mar\", label: \"Frente para o mar\", type: \"Boolean\", cat: \"localidade\", order: 144, },\n { key: \"vista_mar\", label: \"Vista para o mar\", type: \"Boolean\", cat: \"localidade\", order: 146, },\n\n // Estrutura do imóvel\n { key: \"tipo\", label: \"Tipo\", type: \"String\", cat: \"estrutura\", order: 160, },\n { key: \"subtipo\", label: \"Subtipo\", type: \"String\", cat: \"estrutura\", order: 161, },\n { key: \"area_privativa\", label: \"Área privativa\", type: \"Number\", role:\"area\", unit: \"m²\", cat: \"estrutura\", composedLabel: \"{{valueLabel}} priv.\", iconName: \"area\" , order: 163, },\n { key: \"area_total\", label: \"Área Total\", type: \"Number\", role:\"area\", unit: \"m²\", cat: \"estrutura\", composedLabel: \"{{valueLabel}} total\", iconName: \"area\" , order: 164, },\n { key: \"area_terreno\", label: \"Área do terreno\", type: \"Number\", role:\"area\", unit: \"m²\", cat: \"estrutura\", composedLabel: \"{{valueLabel}} terreno\", iconName: \"area\" , order: 165, },\n { key: \"padrao_imovel\", label: \"Padrão do imóvel\", type: \"String\", cat: \"estrutura\", order: 166, },\n { key: \"fase_obra\", label: \"Fase da obra\", type: \"String\", cat: \"estrutura\", order: 167, },\n { key: \"em_condominio\", label: \"Em condomínio\", type: \"Boolean\", cat: \"estrutura\", order: 168, },\n { key: \"finalidade\", label: \"Finalidade (propósito)\", type: \"String\", cat: \"estrutura\", order: 169, },\n { key: \"estado_conservacao\", label: \"Estado da conservação do imóvel\", type: \"String\", cat: \"estrutura\", order: 170, },\n { key: \"estado_imovel\", label: \"Status da obra + uso do imóvel\", type: \"String\", cat: \"estrutura\", order: 171, },\n { key: \"posicao_solar\", label: \"Posição solar\", type: \"String\", cat: \"estrutura\", order: 172, },\n { key: \"posicao_no_terreno\", label: \"Posição do imóvel no terreno\", type: \"String\", cat: \"estrutura\", order: 173, },\n { key: \"tipo_piso\", label: \"Tipo de piso\", type: \"String\", cat: \"estrutura\", order: 174, },\n { key: \"tipos_piso\", label: \"Tipos de piso\", type: \"String[]\", cat: \"estrutura\", order: 175, },\n { key: \"material_imovel\", label: \"Material do imóvel\", type: \"String\", cat: \"estrutura\", order: 176, },\n { key: \"materiais_imovel\", label: \"Materiais do imóvel\", type: \"String[]\", cat: \"estrutura\", order: 177, },\n { key: \"area_construida\", label: \"Área construída\", type: \"Number\", role:\"area\", unit: \"m²\", cat: \"estrutura\", order: 178, },\n { key: \"area_util\", label: \"Área útil\", type: \"Number\", role:\"area\", unit: \"m²\", cat: \"estrutura\", order: 179, },\n { key: \"data_entrega_obra\", label: \"Data de entrega da obra\", type: \"String\", role: \"date\", cat: \"estrutura\", order: 180, },\n { key: \"ano_construcao\", label: \"Ano da construção\", type: \"String\", role: \"year\", cat: \"estrutura\", order: 181, },\n { key: \"topografia_terreno\", label: \"Topografia do terreno\", type: \"String\", cat: \"estrutura\", order: 182, },\n { key: \"pintura\", label: \"Pintura\", type: \"String\", cat: \"estrutura\", order: 183, },\n { key: \"pinturas_revestimentos\", label: \"Pinturas e revestimentos\", type: \"String[]\", cat: \"estrutura\", order: 184, },\n { key: \"tipo_esquadria\", label: \"Tipo de esquadrias\", type: \"String\", cat: \"estrutura\", order: 185, },\n { key: \"tipos_esquadrias\", label: \"Tipos de esquadrias\", type: \"String[]\", cat: \"estrutura\", order: 186, },\n { key: \"tipo_forro\", label: \"Tipo do forro\", type: \"String\", cat: \"estrutura\", order: 187, },\n { key: \"tipos_forros\", label: \"Tipos de forros\", type: \"String[]\", cat: \"estrutura\", order: 188, },\n { key: \"tipos_coberturas\", label: \"Tipos de coberturas\", type: \"String[]\", cat: \"estrutura\", order: 189, },\n { key: \"terreno_comprimento\", label: \"Comprimento do terreno\", type: \"Number\", role:\"distance\", unit: \"m\", cat: \"estrutura\", order: 190, },\n { key: \"terreno_largura\", label: \"Largura do Terreno\", type: \"Number\", role:\"distance\", unit: \"m\", cat: \"estrutura\", order: 191, },\n { key: \"cores\", label: \"Cores do imóvel\", type: \"String[]\", cat: \"estrutura\", order: 192, },\n { key: \"andares\", label: \"Número de andares\", type: \"Number\", role:\"count\", cat: \"estrutura\", order: 193, },\n { key: \"medida_distancia\", label: \"Tipo de medida de distância\", type: \"String\", cat: \"estrutura\", order: 194, },\n { key: \"medida_area\", label: \"Tipo de medida de área\", type: \"String\", cat: \"estrutura\", order: 195, },\n\n // Corretor\n { key: \"corretor_id\", label: \"Corretor → ID\", type: \"String\", cat: \"corretor\", order: 210, },\n { key: \"corretor_nome\", label: \"Corretor → Nome\", type: \"String\", cat: \"corretor\", order: 211, },\n\n // Dependências\n { key: \"dormitorios\", label: \"Dormitórios\", type: \"Number\", role:\"count\", cat: \"dependencias\", composedLabel: \"{{valueLabel}} dormitório{{p:s}}\", iconName: \"bedroom\", order: 230, },\n { key: \"banheiros\", label: \"Banheiros\", type: \"Number\", role:\"count\", cat: \"dependencias\", composedLabel: \"{{valueLabel}} banheiro{{p:s}}\", iconName: \"bathroom\", order: 231, },\n { key: \"suites\", label: \"Suítes\", type: \"Number\", role:\"count\", cat: \"dependencias\", composedLabel: \"Sendo {{valueLabel}} suíte{{p:s}}\", iconName: \"suite\", order: 232, },\n { key: \"vagas_garagem\", label: \"Vagas na garagem\", type: \"Number\", role:\"count\", cat: \"dependencias\", composedLabel: \"{{valueLabel}} vaga{{p:s}}\", iconName: \"garage\", order: 233, },\n\n // Características em geral\n { key: \"mobiliado\", label: \"Mobiliado\", type: \"Boolean\", cat: \"caracteristicas\", order: 250, },\n // { key: \"caracteristicas\", label: \"Características (lista)\", type: \"String[]\", cat: \"caracteristicas\" order: 1, },\n];","export const unitListModel = {\n\tarea: [\n\t\t{\n\t\t\tkey: \"m²\",\n\t\t\taliases: [\"m2\"],\n\t\t\tlabel: \"m²\",\n\t\t\tlabelDisplay: \"Metros quadrados\",\n\t\t},\n\t\t{\n\t\t\tkey: \"ha\",\n\t\t\taliases: [\"hectare\", \"hectares\"],\n\t\t\tlabel: \"ha\",\n\t\t\tlabelDisplay: \"Hectares\",\n\t\t},\n\t\t{\n\t\t\tkey: \"km²\",\n\t\t\taliases: [\"km2\"],\n\t\t\tlabel: \"km²\",\n\t\t\tlabelDisplay: \"Quilômetros quadrados\",\n\t\t},\n\t],\n\n\tlength: [\n\t\t{\n\t\t\tkey: \"m\",\n\t\t\taliases: [\"metro\", \"meter\", \"meters\"],\n\t\t\tlabel: \"m\",\n\t\t\tlabelDisplay: \"Metros\",\n\t\t},\n\t\t{\n\t\t\tkey: \"km\",\n\t\t\taliases: [\"quilometro\", \"kilometer\", \"kilometers\"],\n\t\t\tlabel: \"km\",\n\t\t\tlabelDisplay: \"Quilômetros\",\n\t\t},\n\t],\n\n\tcurrency: [\n\t\t{\n\t\t\tkey: \"BRL\",\n\t\t\taliases: [],\n\t\t\tlabel: \"R$\",\n\t\t\tlabelDisplay: \"Real Brasileiro\",\n\t\t\tlocale: \"pt-BR\",\n\t\t},\n\t],\n};\n\n// {\n// \tkey: \"cm\",\n// \taliases: [\"centimetro\", \"centimeter\", \"centimeters\"],\n// \tlabel: \"cm\",\n// \tlabelDisplay: \"Centímetros\",\n// },\n// {\n// \tkey: \"mm\",\n// \taliases: [\"milimetro\", \"millimeter\", \"millimeters\"],\n// \tlabel: \"mm\",\n// \tlabelDisplay: \"Milímetros\",\n// },\n// {\n// \tkey: \"ft\",\n// \taliases: [\"foot\", \"feet\"],\n// \tlabel: \"ft\",\n// \tlabelDisplay: \"Pés\",\n// },\n// {\n// \tkey: \"in\",\n// \taliases: [\"inch\", \"inches\"],\n// \tlabel: \"in\",\n// \tlabelDisplay: \"Polegadas\",\n// },\n// {\n// \tkey: \"USD\",\n// \taliases: [\"$\", \"dolar\", \"dólar\", \"usd\"],\n// \tlabel: \"US$\",\n// \tlabelDisplay: \"Dólar Americano\",\n// \tlocale: \"en-US\",\n// },\n// {\n// \tkey: \"EUR\",\n// \taliases: [\"€\", \"euro\", \"eur\"],\n// \tlabel: \"€\",\n// \tlabelDisplay: \"Euro\",\n// \tlocale: \"de-DE\",\n// },\n// {\n// \tkey: \"GBP\",\n// \taliases: [\"£\", \"libra\", \"pound\", \"gbp\"],\n// \tlabel: \"£\",\n// \tlabelDisplay: \"Libra Esterlina\",\n// \tlocale: \"en-GB\",\n// },\n\nexport const formatListModel = {\n\tnumber: [\n\t\t{\n\t\t\tkey: \"integer\",\n\t\t\tlabel: \"Inteiro\",\n\t\t\tlabelDisplay: \"Número inteiro\",\n\t\t\tpattern: \"0\",\n\t\t},\n\t\t{\n\t\t\tkey: \"decimal\",\n\t\t\tlabel: \"Decimal\",\n\t\t\tlabelDisplay: \"Número decimal\",\n\t\t\tpattern: \"0.00\",\n\t\t},\n\t\t{\n\t\t\tkey: \"percentage\",\n\t\t\tlabel: \"Porcentagem\",\n\t\t\tlabelDisplay: \"Porcentagem\",\n\t\t\tpattern: \"0.00%\",\n\t\t},\n\t],\n\n\tcurrency: [\n\t\t{\n\t\t\tkey: \"currency\",\n\t\t\tlabel: \"Moeda\",\n\t\t\tlabelDisplay: \"Formato monetário\",\n\t\t\tpattern: \"¤#,##0.00\",\n\t\t},\n\t\t{\n\t\t\tkey: \"currency_compact\",\n\t\t\tlabel: \"Moeda compacta\",\n\t\t\tlabelDisplay: \"Formato monetário compacto\",\n\t\t\tpattern: \"¤#,##0\",\n\t\t},\n\t],\n\n\tdate: [\n\t\t{\n\t\t\tkey: \"date\",\n\t\t\tlabel: \"Data\",\n\t\t\tlabelDisplay: \"Data (DD/MM/AAAA)\",\n\t\t\tpattern: \"dd/MM/yyyy\",\n\t\t},\n\t\t{\n\t\t\tkey: \"datetime\",\n\t\t\tlabel: \"Data e hora\",\n\t\t\tlabelDisplay: \"Data e hora completa\",\n\t\t\tpattern: \"dd/MM/yyyy HH:mm\",\n\t\t},\n\t\t{\n\t\t\tkey: \"year\",\n\t\t\tlabel: \"Ano\",\n\t\t\tlabelDisplay: \"Apenas ano\",\n\t\t\tpattern: \"yyyy\",\n\t\t},\n\t\t{\n\t\t\tkey: \"month_year\",\n\t\t\tlabel: \"Mês/Ano\",\n\t\t\tlabelDisplay: \"Mês e ano\",\n\t\t\tpattern: \"MM/yyyy\",\n\t\t},\n\t],\n\n\ttext: [\n\t\t{\n\t\t\tkey: \"uppercase\",\n\t\t\tlabel: \"Maiúscula\",\n\t\t\tlabelDisplay: \"Texto em maiúscula\",\n\t\t},\n\t\t{\n\t\t\tkey: \"lowercase\",\n\t\t\tlabel: \"Minúscula\",\n\t\t\tlabelDisplay: \"Texto em minúscula\",\n\t\t},\n\t\t{\n\t\t\tkey: \"capitalize\",\n\t\t\tlabel: \"Capitalizado\",\n\t\t\tlabelDisplay: \"Primeira letra maiúscula\",\n\t\t},\n\t\t{\n\t\t\tkey: \"title\",\n\t\t\tlabel: \"Título\",\n\t\t\tlabelDisplay: \"Cada palavra capitalizada\",\n\t\t},\n\t],\n\n\t// Em desenvolvimento futuro\n\t// color: [\n\t// \t{\n\t// \t\tkey: \"rgb\",\n\t// \t\tlabel: \"RGB\",\n\t// \t},\n\t// \t{\n\t// \t\tkey: \"cmyk\",\n\t// \t\tlabel: \"CMYK\",\n\t// \t},\n\t// ],\n};","import { PropertyAttributesModel } from \"../models/property-attributes-model\";\nimport { mergeDeepLeft } from \"ramda\";\n\nexport function verifyAttrKeyInPropertyV3Model(key: string) {\n\treturn PropertyAttributesModel.find((attr) => attr.key == key);\n}\n\n// Mescla dados estendidos sobre o model de atributos padrão do Property V3\n\nexport function mergePropertyAttributesModel(base: any[], overrides: any[]) {\n\t// Indexa os atributos da base por key\n\tconst map = new Map(base.map((attr) => [attr.key, { ...attr }]));\n\n\tfor (const overrideAttr of overrides) {\n\t\tconst baseAttr = map.get(overrideAttr.key);\n\n\t\tif (baseAttr) {\n\t\t\t// Atualiza apenas os campos existentes no overrideAttr\n\t\t\tfor (const field in overrideAttr) {\n\t\t\t\t// Sobrescreve campo do base apenas se estiver presente no override\n\t\t\t\t(baseAttr as any)[field] = overrideAttr[field];\n\t\t\t}\n\t\t\tmap.set(overrideAttr.key, baseAttr);\n\t\t} else {\n\t\t\t// Novo atributo que não existe no base\n\t\t\tmap.set(overrideAttr.key, { ...overrideAttr });\n\t\t}\n\t}\n\n\treturn Array.from(map.values());\n}\n\nexport function sortAttributes(attributes: any[], sortKeys: string[] = []) {\n\treturn [...attributes].sort((a, b) => {\n\t\tfor (const rule of sortKeys) {\n\t\t\tconst [field, direction] = rule.split(\":\");\n\t\t\tconst valA = a[field];\n\t\t\tconst valB = b[field];\n\n\t\t\tif (valA === undefined || valB === undefined) continue;\n\n\t\t\tconst isAsc = direction === \"asc\";\n\t\t\tconst isDesc = direction === \"desc\";\n\n\t\t\tif (!isAsc && !isDesc) {\n\t\t\t\tconsole.warn(`Direção inválida: \"${direction}\". Use \"asc\" ou \"desc\".`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (valA < valB) return isAsc ? -1 : 1;\n\t\t\tif (valA > valB) return isAsc ? 1 : -1;\n\t\t}\n\t\treturn 0;\n\t});\n}\n\nexport const preparaAttrValueLabel = function (valor: any) {\n\treturn Array.isArray(valor)\n\t\t? `${valor.slice(0, -1).join(\", \")} e ${valor[valor.length - 1]}`\n\t\t: valor;\n};","import type { PropertyModel, Attribute } from \"../../models/property-type.schema\"\nimport * as fs from 'fs'\nimport * as path from 'path'\n\n// Utilitários para randomização\nfunction randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min\n}\n\nfunction randomChoice<T>(array: T[]): T {\n return array[Math.floor(Math.random() * array.length)]\n}\n\nfunction randomBoolean(probability = 0.5): boolean {\n return Math.random() < probability\n}\n\nfunction randomFromArray<T>(array: T[], count: number): T[] {\n const shuffled = [...array].sort(() => 0.5 - Math.random())\n return shuffled.slice(0, count)\n}\n\n// Carrega e cache do profiling report\nlet profilingCache: Record<string, any[]> | null = null\n\nfunction loadProfilingReport(): Record<string, any[]> {\n if (profilingCache) {\n return profilingCache\n }\n\n try {\n const reportPath = path.join(__dirname, '..', '..', '..', '__dev__', 'data', 'property-v3-profiling', 'profiling-report.json')\n const reportData = fs.readFileSync(reportPath, 'utf8')\n profilingCache = JSON.parse(reportData)\n return profilingCache!\n } catch (error) {\n console.warn('Não foi possível carregar o profiling report, usando valores padrão')\n return {}\n }\n}\n\n// Cidades brasileiras expandidas\nconst CITIES = [\n { \n cidade: \"São Paulo\", estado: \"SP\", cep: \"01000-000\",\n bairros: [\"Vila Madalena\", \"Pinheiros\", \"Moema\", \"Itaim Bibi\", \"Jardins\", \"Vila Olímpia\", \"Brooklin\", \"Campo Belo\"]\n },\n { \n cidade: \"Rio de Janeiro\", estado: \"RJ\", cep: \"20000-000\",\n bairros: [\"Copacabana\", \"Ipanema\", \"Barra da Tijuca\", \"Botafogo\", \"Leblon\", \"Flamengo\", \"Tijuca\", \"Vila Isabel\"]\n },\n { \n cidade: \"Belo Horizonte\", estado: \"MG\", cep: \"30000-000\",\n bairros: [\"Savassi\", \"Funcionários\", \"Lourdes\", \"Belvedere\", \"Santo Agostinho\", \"Carmo\", \"Serra\", \"Buritis\"]\n },\n { \n cidade: \"Curitiba\", estado: \"PR\", cep: \"80000-000\",\n bairros: [\"Batel\", \"Água Verde\", \"Centro\", \"Bigorrilho\", \"Cabral\", \"Ahú\", \"Juvevê\", \"Rebouças\"]\n },\n { \n cidade: \"Florianópolis\", estado: \"SC\", cep: \"88000-000\",\n bairros: [\"Centro\", \"Trindade\", \"Lagoa da Conceição\", \"Jurerê\", \"Canasvieiras\", \"Ingleses\", \"Córrego Grande\", \"Santa Mônica\"]\n },\n]\n\n// Características adicionais para casas\nconst HOUSE_FEATURES = [\n \"Jardim\", \"Quintal amplo\", \"Área gourmet\", \"Churrasqueira\", \"Piscina\", \"Sauna\", \"Academia\",\n \"Escritório\", \"Biblioteca\", \"Lavabo\", \"Dispensa\", \"Closet\", \"Varanda\", \"Terraço\",\n \"Edícula\", \"Casa de caseiro\", \"Playground\", \"Quadra\", \"Campo de futebol\", \"Horta\",\n \"Pomar\", \"Gazebo\", \"Deck\", \"Spa\", \"Cinema\", \"Adega\", \"Bar\", \"Sala de jogos\"\n]\n\nconst SECURITY_FEATURES = [\n \"Portão eletrônico\", \"Interfone\", \"Câmeras de segurança\", \"Alarme\", \"Cerca elétrica\",\n \"Portaria 24h\", \"Guarita\", \"Rondas\", \"Controle de acesso\", \"Monitoramento\"\n]\n\nconst FINISHING_TYPES = [\n \"Alto padrão\", \"Médio padrão\", \"Padrão popular\", \"Luxo\", \"Ultra luxo\",\n \"Rústico\", \"Moderno\", \"Clássico\", \"Contemporâneo\", \"Minimalista\"\n]\n\nconst ORIENTATION_TYPES = [\n \"Norte\", \"Sul\", \"Leste\", \"Oeste\", \"Nordeste\", \"Noroeste\", \"Sudeste\", \"Sudoeste\"\n]\n\nconst CONSERVATION_STATES = [\n \"Novo\", \"Excelente\", \"Muito bom\", \"Bom\", \"Regular\", \"A reformar\", \"Em obras\"\n]\n\n// Gerador de atributos baseado no profiling report\nfunction generateEnhancedAttributes(tipo: string): Attribute[] {\n const attributes: Attribute[] = []\n const profiling = loadProfilingReport()\n \n // Função helper para pegar valor do profiling\n function getProfilingValue(key: string): any {\n const profilingKey = `attributes.${key}`\n const values = profiling[profilingKey]\n return values && values.length > 0 ? randomChoice(values) : null\n }\n\n // Função helper para pegar valor numérico do profiling \n function getProfilingNumber(key: string, fallbackMin: number, fallbackMax: number): number {\n const value = getProfilingValue(key)\n if (typeof value === 'number') {\n return value\n }\n return randomInt(fallbackMin, fallbackMax)\n }\n\n // Tipo (obrigatório)\n attributes.push({ key: \"tipo\", value: tipo })\n\n // Operação baseada no profiling\n const operacao = getProfilingValue(\"operacao\") || [\"venda\"]\n attributes.push({ key: \"operacao\", value: Array.isArray(operacao) ? operacao : [operacao] })\n\n // Localização baseada no profiling\n const cidade = getProfilingValue(\"endereco_cidade\") || \"São Paulo\"\n const estado = getProfilingValue(\"endereco_estado\") || \"SP\"\n const bairro = getProfilingValue(\"endereco_bairro\") || \"Centro\"\n const cep = getProfilingValue(\"endereco_cep\") || \"01000-000\"\n \n attributes.push(\n { key: \"endereco_cidade\", value: cidade },\n { key: \"endereco_estado\", value: estado },\n { key: \"endereco_bairro\", value: bairro },\n { key: \"endereco_cep\", value: cep },\n { key: \"endereco_numero\", value: getProfilingNumber(\"endereco_numero\", 10, 9999) }\n )\n\n // Adicionar outros campos do profiling se existirem\n const logradouro = getProfilingValue(\"endereco_logradouro\")\n if (logradouro) {\n attributes.push({ key: \"endereco_logradouro\", value: logradouro })\n }\n\n const complemento = getProfilingValue(\"endereco_complemento\")\n if (complemento) {\n attributes.push({ key: \"endereco_complemento\", value: complemento })\n }\n\n // Valores baseados no profiling\n const valorVenda = getProfilingNumber(\"valor_venda\", 200000, 2000000)\n if (valorVenda && (Array.isArray(operacao) ? operacao.includes(\"venda\") : operacao === \"venda\")) {\n attributes.push({ key: \"valor_venda\", value: valorVenda })\n }\n\n const valorLocacao = getProfilingNumber(\"valor_locacao\", 800, 8000)\n if (valorLocacao && (Array.isArray(operacao) ? operacao.includes(\"locacao\") : operacao === \"locacao\")) {\n attributes.push({ key: \"valor_locacao\", value: valorLocacao })\n }\n\n // Características físicas baseadas no profiling\n if (tipo !== \"Terreno\") {\n const dormitorios = getProfilingNumber(\"dormitorios\", 1, 5)\n const banheiros = getProfilingNumber(\"banheiros\", 1, 4)\n const suites = getProfilingNumber(\"suites\", 0, 3)\n const areaUtil = getProfilingNumber(\"area_util\", 40, 400)\n \n attributes.push(\n { key: \"dormitorios\", value: dormitorios },\n { key: \"banheiros\", value: banheiros },\n { key: \"suites\", value: suites },\n { key: \"area_util\", value: areaUtil }\n )\n }\n\n // Área total baseada no profiling\n const areaTotal = getProfilingNumber(\"area_total\", 50, 500)\n if (areaTotal) {\n attributes.push({ key: \"area_total\", value: areaTotal })\n }\n\n // Vagas baseadas no profiling\n const vagas = getProfilingNumber(\"vagas\", 1, 4)\n if (vagas) {\n attributes.push({ key: \"vagas\", value: vagas })\n }\n\n // Características específicas baseadas no profiling\n const caracteristicas = profiling[\"attributes.caracteristicas\"]\n if (caracteristicas && caracteristicas.length > 0) {\n const selectedCaracteristicas = randomFromArray(caracteristicas, randomInt(2, 6))\n attributes.push({ key: \"caracteristicas\", value: selectedCaracteristicas })\n }\n\n // Outros campos do profiling\n const mobiliado = getProfilingValue(\"mobiliado\")\n if (mobiliado !== null) {\n attributes.push({ key: \"mobiliado\", value: mobiliado })\n }\n\n const financiavel = getProfilingValue(\"financiavel\")\n if (financiavel !== null) {\n attributes.push({ key: \"financiavel\", value: financiavel })\n }\n\n const aceitaPermuta = getProfilingValue(\"aceita_pe