# Cómo Añadir un Nuevo Scraper

Esta guía explica paso a paso cómo integrar un portal inmobiliario nuevo en el sistema.

---

## 1. Crear el archivo del scraper

Crea `src/scrapers/miPortalScraper.ts` con esta estructura:

```typescript
import * as cheerio from "cheerio";
import { Property } from "../types/property";
import { BaseScraper } from "./baseScraper";

export class MiPortalScraper extends BaseScraper {
  constructor() {
    super("https://www.miportal.es", "MiPortal");
  }

  async scrape(): Promise<Property[]> {
    const zones = await this.getActiveZones();
    const targets = zones.length > 0
      ? zones.map(z => `${this.baseUrl}/venta/${z.slug}`)
      : [`${this.baseUrl}/venta/lliria`];   // fallback por si no hay zonas

    const properties: Property[] = [];
    const seenUrls = new Set<string>();       // previene duplicados en esta ejecución

    for (const url of targets) {
      const html = await this.fetchHtml(url);
      if (!html) continue;

      const $ = cheerio.load(html);

      // Ajusta el selector al HTML real del portal
      $("article.property-card").each((_i, el) => {
        try {
          const title = this.sanitizeText($(el).find("h2").text() || "Propiedad");
          const priceRaw = $(el).find(".price").text();
          const price = this.parsePrice(priceRaw);         // convierte "150.000 €" → 150000
          const href = $(el).find("a").attr("href") || "";
          const url2 = href.startsWith("http") ? href : `${this.baseUrl}${href}`;
          const location = this.sanitizeText($(el).find(".location").text());
          const rooms = parseInt($(el).find(".rooms").text()) || 0;
          const size_m2 = parseInt($(el).find(".size").text()) || 0;
          const image_url = $(el).find("img").attr("src") || "";

          if (price > 0 && href && !seenUrls.has(url2)) {
            seenUrls.add(url2);
            properties.push({
              title,
              price,
              location,
              municipality: this.detectMunicipality(location, zones),  // detección automática
              property_type: this.normalizeType(title),                 // normalización automática
              rooms,
              size_m2,
              url: url2,
              source: this.sourceName,
              image_url,
            });
          }
        } catch { /* ignorar elemento malformado */ }
      });
    }

    return this.validateResults(properties);  // filtra precio=0 / url vacía
  }
}
```

---

## 2. Exportar en el índice

Añade la exportación en `src/scrapers/index.ts`:

```typescript
export { MiPortalScraper } from "./miPortalScraper";
```

---

## 3. Registrar en la base de datos

Opción A — Añadir a `src/config/initDb.ts` (persistente tras reinstalación):

```typescript
// Dentro del bloque INSERT IGNORE INTO portals:
('MiPortal', 'miportal', 'MiPortalScraper', 1),
```

Luego ejecuta:
```powershell
npx ts-node src/config/initDb.ts
```

Opción B — Insertar directamente en MySQL (phpMyAdmin o terminal):
```sql
INSERT IGNORE INTO portals (name, slug, scraper_class, active)
VALUES ('MiPortal', 'miportal', 'MiPortalScraper', 1);
```

---

## 4. Registrar en el SCRAPER_MAP

En `src/services/propertyService.ts`, importa y añade la clase:

```typescript
import {
  // ... importaciones existentes ...
  MiPortalScraper,
} from "../scrapers/index";

const SCRAPER_MAP: Record<string, new () => any> = {
  // ... entradas existentes ...
  MiPortalScraper,
};
```

---

## 5. Compilar y reiniciar

```powershell
npx tsc
pm2 restart lliria-properties
pm2 save --force
```

---

## Usar Playwright si el portal tiene JavaScript dinámico

Si el portal carga el contenido con JavaScript (React, Vue...), extiende `PlaywrightBaseScraper`:

```typescript
import * as cheerio from "cheerio";
import { Property } from "../types/property";
import { PlaywrightBaseScraper } from "./playwrightBaseScraper";

export class MiPortalPlaywrightScraper extends PlaywrightBaseScraper {
  protected waitForSelector = ".property-card";  // espera a que JS renderice esto
  protected pageTimeoutMs = 40000;

  constructor() {
    super("https://www.miportal.es", "MiPortal");
  }

  async scrape(): Promise<Property[]> {
    const zones = await this.getActiveZones();
    // ... igual que el ejemplo anterior pero usando:
    const html = await this.fetchHtmlPlaywright(url, this.waitForSelector);
    // ...
  }
}
```

Registra el Playwright scraper en el `SCRAPER_MAP` bajo la clave `MiPortalScraper` para que se use automáticamente cuando el portal esté activo.

---

## Métodos Disponibles en BaseScraper

| Método                                            | Descripción                                     |
| ------------------------------------------------- | ----------------------------------------------- |
| `this.fetchHtml(url)`                             | Descarga HTML con axios, reintentos automáticos |
| `this.parsePrice(raw)`                            | Convierte "150.000 €" o "€150,000" a número     |
| `this.sanitizeText(text)`                         | Limpia espacios y caracteres especiales         |
| `this.normalizeType(title, category?)`            | Tipo canónico desde título/categoría            |
| `this.detectMunicipality(text, zones, fallback?)` | Detecta municipio del texto de ubicación        |
| `this.getActiveZones()`                           | Zonas activas de BD (caché 60s)                 |
| `this.validateResults(props)`                     | Filtra entradas inválidas y loguea estadísticas |

---

## Depuración

Para probar un scraper en aislado:

```typescript
// test-scraper.ts (ejecutar con: npx ts-node test-scraper.ts)
import { MiPortalScraper } from "./src/scrapers/miPortalScraper";

(async () => {
  const scraper = new MiPortalScraper();
  const results = await scraper.scrape();
  console.log(`Encontradas: ${results.length} propiedades`);
  console.log(results.slice(0, 3));
})();
```

```powershell
npx ts-node test-scraper.ts
```
