1
2
3
4
5import { describe, it, expect } from '@jest/globals';
6import { mapApiProperty, mapSearchListingItem, enrichWithDetail } from '../../src/data-mapper.js';
7
8
9const rawProp = {
10 id: '191315199',
11 IDinmobiliarias: '175236257',
12 activo: '1',
13 updated: '2026-03-13',
14 direccion: 'Baluarte Del Puerto, Calle 7A, Bellavista',
15 IDpais: '3',
16 fechaReg: '2024-09-04',
17 visitas: '38606',
18 consultas: '9',
19 link: 'baluarte-del-puerto-apartamento-en-venta-en-bellavista-cartagena/191315199',
20 zona: 'Bellavista',
21 departamento: 'Bolívar',
22 img: 'https://cdn2.infocasas.com.uy/repo/img/th.320x280.66d8cb847ce19_baluarte.jpg',
23 operacion: 'Venta',
24 tipoPropiedad: 'Apartamentos',
25 IDtipos: '2',
26 tipoListing: 0,
27 precio: '$ 276.500.000',
28 precioUSD: 82929.93,
29 monto: '276500000',
30 IDmonedas: '4',
31 m2: '53',
32 dormitorios: '2',
33 banios: '2',
34 lat: '10.37304770000000000000',
35 lng: '-75.50456190000000000000',
36 codigo: 'FRS62EF2',
37 codigoProyecto: 'FRAF6DAC',
38 pais: 'Colombia',
39 nombreInmobiliaria: 'PROMOTORA BALUARTE DEL PUERTO',
40};
41
42describe('data-mapper — mapApiProperty', () => {
43 it('pricePerSqm calculado correctamente', () => {
44 const item = mapApiProperty(rawProp);
45
46 expect(item.pricePerSqm).toBe(5216981);
47 });
48
49 it('pricePerSqm es null cuando área es 0', () => {
50 const item = mapApiProperty({ ...rawProp, m2: '0' });
51 expect(item.pricePerSqm).toBeNull();
52 });
53
54 it('pricePerSqm es null cuando área no está definida', () => {
55 const item = mapApiProperty({ ...rawProp, m2: null });
56 expect(item.pricePerSqm).toBeNull();
57 });
58
59 it('operationType normalizado: Venta → sale', () => {
60 const item = mapApiProperty(rawProp);
61 expect(item.operationType).toBe('sale');
62 });
63
64 it('operationType normalizado: Alquiler → rent', () => {
65 const item = mapApiProperty({ ...rawProp, operacion: 'Alquiler' });
66 expect(item.operationType).toBe('rent');
67 });
68
69 it('operationType normalizado: Arriendo → rent', () => {
70 const item = mapApiProperty({ ...rawProp, operacion: 'Arriendo' });
71 expect(item.operationType).toBe('rent');
72 });
73
74 it('propertyType normalizado: Apartamentos → apartment', () => {
75 const item = mapApiProperty(rawProp);
76 expect(item.propertyType).toBe('apartment');
77 });
78
79 it('isActive true cuando activo es "1"', () => {
80 const item = mapApiProperty(rawProp);
81 expect(item.isActive).toBe(true);
82 });
83
84 it('isActive false cuando activo es "0"', () => {
85 const item = mapApiProperty({ ...rawProp, activo: '0' });
86 expect(item.isActive).toBe(false);
87 });
88
89 it('latitude como número', () => {
90 const item = mapApiProperty(rawProp);
91 expect(item.latitude).toBeCloseTo(10.3730477, 5);
92 });
93
94 it('longitude como número', () => {
95 const item = mapApiProperty(rawProp);
96 expect(item.longitude).toBeCloseTo(-75.5045619, 5);
97 });
98
99 it('listedAt como ISO 8601', () => {
100 const item = mapApiProperty(rawProp);
101 expect(item.listedAt).toBe('2024-09-04T00:00:00.000Z');
102 });
103
104 it('updatedAt como ISO 8601', () => {
105 const item = mapApiProperty(rawProp);
106 expect(item.updatedAt).toBe('2026-03-13T00:00:00.000Z');
107 });
108
109 it('listingUrl construida correctamente', () => {
110 const item = mapApiProperty(rawProp);
111 expect(item.listingUrl).toBe(
112 'https://www.fincaraiz.com.co/baluarte-del-puerto-apartamento-en-venta-en-bellavista-cartagena/191315199'
113 );
114 });
115
116 it('listingId como string', () => {
117 const item = mapApiProperty(rawProp);
118 expect(item.listingId).toBe('191315199');
119 });
120
121 it('price como número entero', () => {
122 const item = mapApiProperty(rawProp);
123 expect(item.price).toBe(276500000);
124 });
125
126 it('areaSqm como número entero', () => {
127 const item = mapApiProperty(rawProp);
128 expect(item.areaSqm).toBe(53);
129 });
130
131 it('bedrooms y bathrooms como números', () => {
132 const item = mapApiProperty(rawProp);
133 expect(item.bedrooms).toBe(2);
134 expect(item.bathrooms).toBe(2);
135 });
136
137 it('listingViews y listingInquiries como números', () => {
138 const item = mapApiProperty(rawProp);
139 expect(item.listingViews).toBe(38606);
140 expect(item.listingInquiries).toBe(9);
141 });
142});
143
144describe('data-mapper — enrichWithDetail', () => {
145
146
147 const detail = {
148 stratum: 4,
149 garage: 1,
150 floorsCount: 12,
151 floor: 5,
152 construction_year: 2018,
153
154 m2Terrain: null,
155 m2Terrace: 8,
156 commonExpenses: { amount: 350000, currency: { id: 4, name: '$' } },
157 price_admin_usd: 104.95,
158 price_amount_usd: 82929.93,
159 facilities: [
160 { id: 1, group: 'Seguridad', name: 'Portería' },
161 { id: 2, group: 'Recreación', name: 'Piscina' },
162 { id: 3, group: 'Bienestar', name: 'Gimnasio' },
163 ],
164 images: [
165 { id: 1, image: 'https://cdn4.fincaraiz.com.co/img/1.jpg', tag: null },
166 { id: 2, image: 'https://cdn4.fincaraiz.com.co/img/2.jpg', tag: null },
167 ],
168 owner: {
169 id: 12345,
170 name: 'PROMOTORA BALUARTE DEL PUERTO',
171 type: 'constructora',
172 masked_phone: '(57) 301 ***-**34',
173 has_whatsapp: true,
174 logo: 'https://cdn4.fincaraiz.com.co/logo/12345.png',
175 inmoPropsLink: 'https://www.fincaraiz.com.co/inmobiliaria/promotora-baluarte/',
176 },
177 locations: {
178 city: [{ id: '45-uuid', name: 'Cartagena', slug: ['city-colombia-13-001'] }],
179 state: [{ id: '5-uuid', name: 'Bolívar' }],
180 neighbourhood: [{ id: '3847-uuid', name: 'Bellavista', slug: ['neighbourhood-colombia-13-001'] }],
181 location_point: 'POINT (-75.5045619 10.3730477)',
182 },
183 has_video: false,
184 tour3d: null,
185 isProject: false,
186 isProjectUnit: false,
187 finances: true,
188 barter: false,
189 discount: null,
190 };
191
192 it('stratum extraído como número', () => {
193 const item = mapApiProperty(rawProp);
194 enrichWithDetail(item, detail);
195 expect(item.stratum).toBe(4);
196 });
197
198 it('floorsCount extraído desde camelCase', () => {
199 const item = mapApiProperty(rawProp);
200 enrichWithDetail(item, detail);
201 expect(item.floorsCount).toBe(12);
202 });
203
204 it('adminFee extraído desde commonExpenses (camelCase)', () => {
205 const item = mapApiProperty(rawProp);
206 enrichWithDetail(item, detail);
207 expect(item.adminFee).toBe(350000);
208 });
209
210 it('agentHasWhatsapp booleano', () => {
211 const item = mapApiProperty(rawProp);
212 enrichWithDetail(item, detail);
213 expect(item.agentHasWhatsapp).toBe(true);
214 });
215
216 it('agentPortfolioUrl desde inmoPropsLink (camelCase)', () => {
217 const item = mapApiProperty(rawProp);
218 enrichWithDetail(item, detail);
219 expect(item.agentPortfolioUrl).toContain('promotora-baluarte');
220 });
221
222 it('amenityNames calculado como array de strings', () => {
223 const item = mapApiProperty(rawProp);
224 enrichWithDetail(item, detail);
225 expect(item.amenityNames).toEqual(['Portería', 'Piscina', 'Gimnasio']);
226 });
227
228 it('imageCount calculado', () => {
229 const item = mapApiProperty(rawProp);
230 enrichWithDetail(item, detail);
231 expect(item.imageCount).toBe(2);
232 });
233
234 it('areaTerraceSqm extraído desde m2Terrace (camelCase)', () => {
235 const item = mapApiProperty(rawProp);
236 enrichWithDetail(item, detail);
237 expect(item.areaTerraceSqm).toBe(8);
238 });
239
240 it('locationPoint parseado desde WKT string POINT(lng lat)', () => {
241 const item = mapApiProperty(rawProp);
242 enrichWithDetail(item, detail);
243 expect(item.locationPoint).toBeDefined();
244 expect(item.locationPoint.lat).toBeCloseTo(10.3730477, 4);
245 expect(item.locationPoint.lng).toBeCloseTo(-75.5045619, 4);
246 });
247
248 it('city y citySlug extraídos correctamente (slug como array)', () => {
249 const item = mapApiProperty(rawProp);
250 enrichWithDetail(item, detail);
251 expect(item.city).toBe('Cartagena');
252 expect(item.citySlug).toBe('city-colombia-13-001');
253 });
254
255 it('neighborhoodId es UUID string (no número)', () => {
256 const item = mapApiProperty(rawProp);
257 enrichWithDetail(item, detail);
258 expect(typeof item.neighborhoodId).toBe('string');
259 });
260
261 it('isProject desde camelCase (isProject NO is_project)', () => {
262 const item = mapApiProperty(rawProp);
263 enrichWithDetail(item, detail);
264 expect(item.isProject).toBe(false);
265 });
266
267 it('financingAvailable desde finances', () => {
268 const item = mapApiProperty(rawProp);
269 enrichWithDetail(item, detail);
270 expect(item.financingAvailable).toBe(true);
271 });
272
273 it('totalMonthlyUsd null para venta', () => {
274 const item = mapApiProperty(rawProp);
275 enrichWithDetail(item, detail);
276 expect(item.totalMonthlyUsd).toBeNull();
277 });
278
279 it('totalMonthlyUsd calculado para arriendo', () => {
280 const item = mapApiProperty({ ...rawProp, operacion: 'Alquiler', monto: '2667000', precioUSD: 800 });
281
282
283 const detailRent = { ...detail, commonExpenses: { amount: 333375 } };
284 enrichWithDetail(item, detailRent);
285 expect(item.adminFeeUsd).toBe(100);
286 expect(item.totalMonthlyUsd).toBe(900);
287 });
288
289 it('enriquecimiento con detail null no crashea', () => {
290 const item = mapApiProperty(rawProp);
291 expect(() => enrichWithDetail(item, null)).not.toThrow();
292 });
293
294 it('agentPhone es null (masked_phone del sitio es siempre truncado)', () => {
295 const item = mapApiProperty(rawProp);
296 const detailWithPhone = {
297 ...detail,
298 owner: { ...detail.owner, masked_phone: '+5760' },
299 };
300 enrichWithDetail(item, detailWithPhone);
301 expect(item.agentPhone).toBeNull();
302 });
303
304 it('bedrooms desde detail.bedrooms (no detail.rooms)', () => {
305 const item = mapApiProperty(rawProp);
306 const detailWithBedrooms = { ...detail, bedrooms: 4, rooms: 0 };
307 enrichWithDetail(item, detailWithBedrooms);
308 expect(item.bedrooms).toBe(4);
309 });
310
311 it('title desde detail.title', () => {
312 const item = mapApiProperty(rawProp);
313 const detailWithTitle = { ...detail, title: 'Apartamento en Venta en Los rosales, Bogotá' };
314 enrichWithDetail(item, detailWithTitle);
315 expect(item.title).toBe('Apartamento en Venta en Los rosales, Bogotá');
316 });
317
318 it('adminFeeUsd calculado desde commonExpenses/rate (no price_admin_usd)', () => {
319
320
321 const item = mapApiProperty({ ...rawProp, monto: '1200000000', precioUSD: 367341 });
322 const detailAdmin = {
323 ...detail,
324 commonExpenses: { amount: 2000000 },
325 price: { amount: 1200000000, currency: { rate: 3266.72 } },
326 price_admin_usd: 367953,
327 };
328 enrichWithDetail(item, detailAdmin);
329
330 expect(item.adminFeeUsd).toBe(612);
331
332 expect(item.adminFeeUsd).not.toBe(367953);
333 });
334});
335
336
337describe('data-mapper — mapSearchListingItem', () => {
338
339 const rawSearch = {
340 id: 193978359,
341 title: 'Apartamento en Venta en Los rosales, Bogotá',
342 rooms: 0,
343 bedrooms: 4,
344 bathrooms: 4,
345 m2: 145,
346 garage: 1,
347 stratum: 5,
348 link: '/apartamento-en-venta-en-los-rosales-bogota/193978359',
349 active: true,
350 price: {
351 amount: 1200000000,
352 admin_included: 1202000000,
353 currency: { id: 4, name: '$', rate: 3266.72 },
354 },
355 commonExpenses: { amount: 2000000, currency: { id: 4, name: '$' } },
356 price_admin_usd: 367953,
357 price_amount_usd: 367341,
358 operation_type: { id: 1, name: 'Venta' },
359 property_type: { id: 2, name: 'Apartamentos' },
360 latitude: 4.6529096,
361 longitude: -74.0495658,
362 locations: {
363 city: [{ id: 'bogota-uuid', name: 'Bogotá', slug: ['city-colombia-11-001'] }],
364 state: [{ id: 'cundinamarca-uuid', name: 'Cundinamarca' }],
365 neighbourhood: [{ id: 'nbh-uuid', name: 'Los Rosales' }],
366 },
367 owner: {
368 id: 174689321,
369 name: 'Julio Herrera',
370 masked_phone: '+5760',
371 has_whatsapp: true,
372 type: 'inmobiliaria',
373 },
374 };
375
376 it('BUG B — bedrooms usa raw.bedrooms, no raw.rooms', () => {
377 const item = mapSearchListingItem(rawSearch);
378 expect(item.bedrooms).toBe(4);
379 expect(item.bedrooms).not.toBe(0);
380 });
381
382 it('BUG C — title mapeado correctamente', () => {
383 const item = mapSearchListingItem(rawSearch);
384 expect(item.title).toBe('Apartamento en Venta en Los rosales, Bogotá');
385 });
386
387 it('BUG D — adminFeeUsd calculado desde commonExpenses/rate, no price_admin_usd', () => {
388 const item = mapSearchListingItem(rawSearch);
389
390 expect(item.adminFeeUsd).toBe(612);
391 expect(item.adminFeeUsd).not.toBe(367953);
392 });
393
394 it('latitude y longitude correctas (Bogotá)', () => {
395 const item = mapSearchListingItem(rawSearch);
396 expect(item.latitude).toBeCloseTo(4.6529096, 4);
397 expect(item.longitude).toBeCloseTo(-74.0495658, 4);
398 });
399
400 it('city extraída de locations.city', () => {
401 const item = mapSearchListingItem(rawSearch);
402 expect(item.city).toBe('Bogotá');
403 });
404
405 it('operationType normalizado: Venta → sale', () => {
406 const item = mapSearchListingItem(rawSearch);
407 expect(item.operationType).toBe('sale');
408 });
409
410 it('priceUsd calculado desde price.amount / price.currency.rate', () => {
411 const item = mapSearchListingItem(rawSearch);
412
413 expect(item.priceUsd).toBe(367341);
414 });
415});