# Etapa 3 - Scraping de dados do produto e geração de texto formatado

## Status

- Situacao: **Concluido**
- Objetivo da etapa: para cada oferta nova (MLB deduplcado), buscar dados reais do produto (titulo, preco De/Por, % OFF) via scraping da pagina ML e gerar texto pronto para repost no canal de destino.

---

## Contexto

Apos a Etapa 2 extrair o `MLB` de cada URL, o pipeline precisa buscar os dados do produto para montar o texto de divulgacao. A API publica do ML frequentemente retorna 403 para IDs de catalogo (`/p/MLB...`), portanto o scraping direto do HTML e o metodo principal.

---

## Problema encontrado e solucao

### Preco errado no scraping (parcela vs preco real)

O seletor `andes-money-amount__fraction` aparece multiplas vezes na pagina ML:
- Preco real no Pix: `R$ 784,00`
- Preco no cartao (sem juros): `R$ 871,11`
- Parcela individual: `R$ 87,11`
- Preco de outra variante, etc.

O codigo anterior pegava o **menor** valor — que era a parcela (R$ 87) — em vez do preco real.

**Solucao implementada — 3 camadas de extracao:**

| Estrategia | O que faz | Confiabilidade |
|-----------|-----------|----------------|
| `initialState` JSON | Busca `price.value` + `original_price.value` no JSON embutido (`__NEXT_DATA__`, `window.initialState`, `__PRELOADED_STATE__`) | Alta — nao tem parcelas |
| CSS context regex | `.ui-pdp-price__second-line` (preco Pix) e `.ui-pdp-price__original-value` (preco tachado) | Media — depende do HTML |
| `application/ld+json` | Schema.org `offers.lowPrice` | Media — pode diferir do Pix |
| `meta itemprop="price"` | Fallback simples | Baixa — nem sempre presente |

### URL com parametros de rastreio causando preco errado

Quando o link vem de pagina social (`meli.la` → `/social/...`), o botao "Ir para produto" gera uma URL com parametros de afiliado do canal de origem:

```
.../p/MLB44665473?matt_event_ts=...&matt_d2id=...#reco_client=home_affiliate-profile&reco_item_pos=0
```

O ML interpreta esses parametros e pode retornar a **variante especifica promovida pelo afiliado original** — que pode ter preco diferente (ex.: modelo 128GB no lugar do 256GB padrao).

**Solucao:** limpar a URL antes do scraping — remover query params e fragment. O link original (`meli.la/XXXXX`) e preservado no texto final para manter a comissao do repost.

```php
$scrapeUrl = strtok($finalUrl, '?#') ?: $finalUrl;
```

### Percentual de desconto diferente do ML

O ML usa `floor()` no calculo de desconto, nao `round()`. Corrigido para coincidir com o valor exibido na pagina.

---

## Fluxo da Etapa 3

```
URL original (meli.la/XXXXX)
    ↓ Etapa 2: resolver + extrair MLB
    ↓
MLB-44665473 + final_url
    ↓ limpar query params da final_url
    ↓ scrape URL limpa (.../p/MLB44665473)
    ↓
titulo, preco, old_price, discount
    ↓ formatar texto
    ↓ salvar formatted_text em processed_offers
    ↓
Texto pronto para repost:

  Smartphone Motorola Moto G15 - 256gb 12gb...

  De: R̶$̶ ̶1̶.̶4̶9̶9̶,̶0̶0̶
  Por: R$ 784,00 (47% OFF)

  Compre aqui: https://meli.la/2SFbpNt
```

---

## Regras de negocio

- O scraping usa a URL **sem** query params e fragment (URL limpa) para garantir o produto padrao.
- O link no texto final e **sempre** a URL original capturada do Telegram (meli.la/...) para preservar rastreio do afiliado de origem ate que o link proprio seja implementado.
- Frete **nao** e exibido: varia por CEP e por assinatura Meli+.
- Condicao do produto (`Produto Novo`) **nao** e exibida: informacao redundante para o contexto de promocoes.
- Se o scraping falhar, a oferta e registrada com `fetch_failed = true`; o pipeline continua sem interromper.
- O calculo de desconto usa `floor()` para coincidir com o exibido no ML.

---

## Banco de dados

### Novos campos em `processed_offers` (migration `2026_03_14_000005`)

| Campo | Tipo | Descricao |
|-------|------|-----------|
| `formatted_text` | text nullable | Texto completo pronto para repost |
| `product_title` | string(500) nullable | Titulo do produto |
| `product_price` | decimal(10,2) nullable | Preco atual em reais |
| `fetch_failed` | boolean default false | Flag: scraping falhou |

---

## Arquivos alterados

| Arquivo | O que mudou |
|---------|-------------|
| `app/Services/Affiliate/MercadoLivreAffiliateService.php` | Novos metodos de extracao de preco via JSON e CSS context; limpeza de URL antes do scraping; `floor()` no desconto |
| `app/Console/Commands/TelegramProcessPending.php` | Integra busca de produto + formatacao de texto; salva `formatted_text` em cada oferta nova |
| `app/Console/Commands/TelegramProcessMl.php` | Remove frete do texto gerado |
| `app/Models/ProcessedOffer.php` | Novos campos no `$fillable`; `register()` aceita `formatted_text`, `product_title`, `product_price`, `fetch_failed`; retorna o modelo criado (antes retornava bool) |
| `database/migrations/2026_03_14_000005_...` | Adiciona colunas `formatted_text`, `product_title`, `product_price`, `fetch_failed` |

---

## Criterios de aceite da Etapa 3

- Para URL direta (`/p/MLB...`): scraping retorna titulo e preco corretos.
- Para URL via `meli.la` (pagina social): scraping retorna os mesmos dados do produto padrao, nao da variante do afiliado original.
- Preco De/Por com % OFF correto (mesmo valor exibido no ML).
- Texto formatado salvo em `processed_offers.formatted_text`.
- Falha no scraping nao interrompe o pipeline; oferta e registrada com `fetch_failed = true`.

---

## Checklist tecnico

- [x] Extracao de preco via `initialState` JSON (`__NEXT_DATA__`, `window.initialState`, `__PRELOADED_STATE__`).
- [x] Extracao de preco via CSS context (`.ui-pdp-price__second-line`, `.ui-pdp-price__original-value`).
- [x] Fallback `application/ld+json` e `meta itemprop="price"`.
- [x] Limpeza de URL antes do scraping (remove `?matt_*` e `#fragment`).
- [x] Desconto calculado com `floor()` (mesmo comportamento do ML).
- [x] `formatted_text` salvo em `processed_offers`.
- [x] Pipeline nao interrompido por falha de scraping (`fetch_failed`).
- [x] Comando `telegram:scrape-debug` para diagnostico de extracao.
- [x] Validado com link real: `meli.la/2SFbpNt` → R$ 784,00 De R$ 1.499,00 (47% OFF).

---

## Comandos disponiveis apos esta etapa

| Comando | Descricao |
|---------|-----------|
| `telegram:process-ml {url}` | Processa URL ML completa e exibe texto gerado |
| `telegram:process-pending` | Processa todas as URLs pendentes do banco |
| `telegram:scrape-debug {url}` | Diagnostica extracao de preco de uma URL ML |
| `telegram:scrape-debug {url} --dump-state` | Dump completo do JSON embutido na pagina |

---

## Proxima etapa (Etapa 4)

**Status: Concluido** — ver `docs/TELEGRAM_ETAPA_4_LINK_AFILIADO.md`

**Objetivo:** gerar link de afiliado proprio (`meli.la` com tag `pechinchinhaboa`) e postar o texto no canal de destino Telegram.

1. **Gerar link de afiliado ML proprio** a partir do MLB extraido.
   - Opcao A: API oficial ML Affiliates (`POST /advertising/short_urls`) — requer OAuth token.
   - Opcao B: URL parametrizada com `matt_from=advertising&matt_tool={SEU_ID}` — requer `customId` de afiliado.
   - Prerequisito: conta ativa no Programa de Afiliados do Mercado Livre.

2. **Postar texto no canal de destino Telegram.**
   - Usar MadelineProto (ja autenticado) para enviar mensagem no canal cadastrado como destino.
   - Comando `telegram:post-offers` — le `processed_offers` com `formatted_text` nao nulo e `fetch_failed = false`, posta e marca como publicado.

3. **Marcar oferta como publicada.**
   - Novo campo `posted_at` em `processed_offers`.
   - Evitar repost de ofertas ja publicadas.
