# Publishing

Publicação multicanal de imóveis — **Sprint 007** · domínio de distribuição.

Namespace: `ImovelZapAi\Publishing\`

**Promessa:** cadastre uma única vez, publique em todos os canais.

## Estrutura

```text
packages/Publishing/src/
├── Domain/           # Enums, exceções, regras puras
├── Application/      # Actions, Queries, DTOs, Ports, Services
├── Infrastructure/   # Eloquent, Jobs, Connectors, Providers
├── Presentation/     # Controllers HTTP, rotas web
└── Shared/           # Metadados do package (`Package::LAYERS`)
```

| Camada | Responsabilidade |
|--------|------------------|
| **Domain** | Status, eventos, exceções, `NormalizedListing` (canônico) |
| **Application** | Orquestração, mapper canônico, serializers, `PublisherInterface` |
| **Infrastructure** | Persistência, fila, conectores de portal |
| **Presentation** | Entrada HTTP (rotas na ficha do imóvel) |
| **Shared** | Constantes e helpers internos do módulo |

## Documentação

- Sprint: [`docs/sprints/SPRINT_007_PROPERTY_PUBLISHING.md`](../../docs/sprints/SPRINT_007_PROPERTY_PUBLISHING.md)
- Blueprint futuro: [`docs/future/blueprints/MARKETPLACE_BLUEPRINT.md`](../../docs/future/blueprints/MARKETPLACE_BLUEPRINT.md)

## Entidades

| Entidade | Model |
|----------|-------|
| Publisher | `PublisherModel` |
| Publication | `PublicationModel` |
| PublicationTarget | `PublicationTargetModel` |
| PublicationLog | `PublicationLogModel` |

## Conectores

| Item | Caminho |
|------|---------|
| Modelo canônico | `Domain/Canonical/NormalizedListing` |
| Mapeamento | `Application/Mappers/ListingMapper` |
| Property → canônico | `Application/Mappers/NormalizedListingMapper` |
| Canônico → VRSync | `Application/Mappers/VrSyncListingMapper` |
| Feed VRSync | `Application/Services/FeedGenerator` |
| Header VRSync | `Application/Services/VrSyncHeaderGenerator` |
| Mídia | `Application/Services/MediaProcessor` |
| Validação XML | `Application/Services/ListingDataFeedXmlValidator` |
| Feed cache | `Application/Services/TenantFeedStorage` + `TenantFeedBuilder` |
| Centro de publicação | `GET /publicacao` → `GetPublicationCenterQuery` |
| Feed Health | `TenantFeedHealthChecker` — 6 verificações no centro |
| Logs de geração | `tenant_feed_generation_logs` + `TenantFeedGenerationLogger` |
| Config tenant | `tenant_publishing_configs` + `TenantPublishingConfigResolver` |
| Config UI | `GET/PUT /publicacao/configuracao` → `publishing.config.*` |
| URL pública | `GET /feeds/{tenant-slug}.xml` → `publishing.feed.public` |
| Serializer XML | `Application/Services/ListingDataFeedXmlSerializer` |
| Interface | `Application/Ports/PublisherInterface` |
| Resolver | `Infrastructure/Services/PublisherConnectorResolver` |
| Internos | `WhatsappCatalogPublisher`, `ExternalLinkPublisher` |
| Exportação | `XmlExportPublisher` — VRSync `ListingDataFeed` |

Para adicionar portal: implementar `PublisherInterface`, registrar na tag `publishing.connectors`.

### Conector oficial — Grupo OLX (Sprint 008)

| Item | Caminho |
|------|---------|
| Módulo | `src/Connectors/GroupOlx/` |
| Namespace | `ImovelZapAi\Publishing\Connectors\GroupOlx\` |
| Ports | `GroupOlxFeedBuilderInterface`, `GroupOlxFeedValidatorInterface`, `GroupOlxPublicFeedInterface`, `GroupOlxConnectorInterface` |
| Mapper | `GroupOlxListingMapper` — NormalizedListing → `GroupOlxMappedListingData` |
| XML Builder | `VrsyncXmlBuilder` — DTO → string XML UTF-8 (sem salvar) |
| Validator | `FeedValidator` — lista de erros (sem Exception) |
| Public Feed | `GET /feed/{slug}/vrsync.xml` — cache + ETag + gzip |
| Queue | `GenerateVrsyncFeedJob` — geração assíncrona do feed |
| Storage | `FeedStorageInterface` — Local / Cloudflare R2, versionado |
| Dashboard | `GetGroupOlxDashboardQuery` — painel no Centro de publicação |
| PublicationLogs | `group_olx_publication_logs` — append-only (nunca excluir) |
| Compatibilidade | `GroupOlxCompatibilitySuite` + relatório em `docs/sprints/reports/` |
| Docs | [`docs/architecture/PublishingHub.md`](../../docs/architecture/PublishingHub.md) |
| Slug | `group_olx` |
| Doc | [`Connectors/GroupOlx/README.md`](src/Connectors/GroupOlx/README.md) |
| Sprint | [`docs/sprints/SPRINT_008_GROUP_OLX_CONNECTOR.md`](../../docs/sprints/SPRINT_008_GROUP_OLX_CONNECTOR.md) |

**Roadmap V2:** Publishing Hub (OLX = primeiro adapter) + Feed Quality (alertas de qualidade do anúncio, sem IA).

## Fila

Publicações são **sempre assíncronas** — fila `publishing` via `ProcessPropertyPublicationJob`.

```bash
docker compose exec -w /var/www/html/backend php php artisan queue:work --queue=publishing
```

## Testes

```bash
docker compose exec -w /var/www/html/backend php php artisan test tests/Feature/Publishing tests/Unit/Publishing
```
