Cómo está construido Opplook.
Opplook ayuda a agencias y freelancers a decidir a qué negocios locales contactar y con qué argumentos. Lo construyo yo solo, Israel Pereira, desde enero de 2026, y todavía está en desarrollo.
El repositorio es privado, así que aquí enseño la arquitectura y las decisiones, incluido lo que aún no está hecho.
En desarrollo · Proyecto en solitario
- tests en 39 archivos
- ~550
- señales de puntuación
- 17
- jobs de CI
- 5
- migraciones
- 26
47 commits entre el 7 de enero y el 8 de octubre de 2026.
Stack
API en Laravel, pensada para que cualquier frontend la consuma.
- Backend
- PHP ^8.4, Laravel ^13.0, Sanctum ^4.3 (Bearer). API-first.
- Frontend
- Separado en otro repositorio. El anterior (Angular 18) está archivado; hay uno nuevo planificado.
- Datos y colas
- MySQL en producción, SQLite en tests. Cola con driver database (Redis planificado, sin hacer).
- Servicios externos
- Stripe (con cliente propio, StripeApiClient), Google Places API (New), Nominatim / OpenStreetMap.
- Testing
- PHPUnit ^12.5, Paratest y Mockery.
- Análisis estático
- Larastan nivel 6 sobre app, config, database y routes con baseline, y phpstan-strict.neon nivel 8 sobre los directorios nuevos. phpat para reglas de arquitectura. Laravel Pint.
Arquitectura
Un job orquesta el pipeline de búsqueda y cada paso externo queda detrás de un contrato.
- 1
Geocodificar la zona
Nominatim / OpenStreetMap convierte la zona en coordenadas.
Geocoding
Interface - 2
Buscar negocios
Google Places API (New) devuelve los negocios de la zona.
Google
Places Interface - 3
Elegibilidad · capa 1
Lista de tipos de lugar bloqueados y rechazo de dominios públicos (.gov, .edu).
Business
Eligibility Service - 4
Relevancia · capa 2
Relevancia del negocio según el sector que vende el usuario.
Sector
Relevance Service - 5
Puntuación
17 señales ponderadas dan una puntuación de oportunidad de 0 a 100.
Signal
Detector Service - 6
Auditoría de la web
HTTPS, viewport móvil, formulario, CTA, WhatsApp, analítica, plataforma, TTFB y SEO.
Web
Audit Job
Servicio externo, detrás de un contratoLógica propia
Contratos y gemelas fake
Cada servicio externo vive detrás de una interfaz y tiene una gemela fake. Un flag de configuración decide cuál se enlaza en el service provider.
GeocodingInterface
Nominatim / OpenStreetMap · gemela Fake*
GooglePlacesInterface
Google Places API (New) · gemela Fake*
WebAuditInterface
WebAuditService · gemela FakeWebAuditService
Capas de app/
- Contracts
- Providers
- Services
- Jobs
- Models
- Http/Controllers/Api
- Domain (Shared, Billing)
Reglas de arquitectura con phpat: los controladores no dependen de infraestructura, HTTP ni base de datos.
Los errores de la API usan un envoltorio uniforme {success, code, message} (ApiErrorCode y ApiException). La auditoría web incluye también heurísticas para detectar píxeles de SEM y anuncios en redes, documentadas como tales: son aproximadas.
Motor de señales
Cada negocio recibe una puntuación de oportunidad de 0 a 100 a partir de señales ponderadas.
17 señales ponderadas
- no_website
- low_reviews
- very_low_reviews
- no_phone
- low_rating
- incomplete_profile
- low_quality_website_url
- no_https
- no_mobile_viewport
- no_contact_form
- no_cta
- no_whatsapp
- no_analytics
- website_platform_wix_or_free
- too_many_reviews
- high_rating
- complete_profile
Los tres últimos, con borde discontinuo, son señales negativas o neutras: restan puntos o no suman.
Etiquetas y sub-puntuaciones
- hot_opportunity
- ≥ 80
- has_potential
- ≥ 60
- needs_investigation
- ≥ 40
- low_priority
- < 40
Junto a la puntuación se calculan una visibility_score y una contact_readiness_score. Hay dos tablas de pesos, V1 y V2, detrás del flag SCORING_V2_ENABLED, con la misma forma de API.
Otros motores
- ProfileFitService · encaje con lo que vende la agencia
- ReasonToContactGenerator · motivo para contactar
- ServiceRecommendationEngine · servicio recomendado
- LeadQualityScorer · calidad del lead
Puertas de calidad y CI
Se ejecuta en cada push y en cada pull request. composer qa lanza lo mismo en local.
- 1
Composer
composer validate --strict y composer audit.
- 2
Lint
Pint y comprobación de TODOs.
- 3
Stan
Larastan con las dos configuraciones y un guard que impide que crezca el baseline.
- 4
Tests · SQLite
Suite completa en paralelo.
- 5
Tests · MySQL
Suite completa con migrate:fresh sobre MySQL.
- Larastan nivel 6 con baseline, más nivel 8 estricto en el código nuevo.
- Tests de arquitectura con phpat: los controladores no dependen de infraestructura, HTTP ni base de datos, ni llaman a HTTP o usan sleep directamente.
- Http::preventStrayRequests() falla cualquier llamada HTTP sin simular.
Decisiones y por qué
Lo que cambié o fijé, y la razón.
El giro del producto
La propuesta del 14 de septiembre de 2026 cambió Opplook de listas de leads que se desbloqueaban con créditos a una herramienta de ventas por suscripción para agencias de marketing y diseño web y freelancers de España.
El modelo anterior tenía el cliente equivocado, chocaba con las condiciones de Google Maps Platform (almacenar y exportar contenido de Places), era difícil de poner precio y no tenía puertas de calidad.
Puertas de calidad antes que funciones
La fase 0 fue Pint, Larastan, Paratest, CI y la subida a PHP 8.4 y Laravel 13. El código heredado tenía tests que fallaban y tests que llamaban a la red real, así que no se podía confiar en nada de lo que se construyera encima.
Contratos y fakes para cada servicio externo
Cada servicio externo (GooglePlacesInterface, GeocodingInterface, WebAuditInterface) tiene una gemela Fake* enlazada por configuración. Http::preventStrayRequests() hace fallar cualquier llamada HTTP no simulada en los tests: ninguna prueba toca la red por accidente.
app/Providers/WebAuditServiceProvider.php $fakeMode = config('webaudit.fake_mode', false);$class = $fakeMode ? FakeWebAuditService::class : WebAuditService::class;$this->app->singleton(WebAuditInterface::class, $class);Scoring V2 detrás de un feature flag
Las dos tablas de pesos, V1 y V2, conviven detrás de SCORING_V2_ENABLED y devuelven la misma forma de API. V2 sube el peso de los fallos detectados en la auditoría web, que son más fáciles de vender que un simple «no tiene web», sin cambiar el contrato.
app/Services/SignalDetectorService.php 'no_website' => 15, // Lowered: harder sell than web-audit gaps'no_https' => 30, // Raised: SSL upsell is easy + urgent'no_mobile_viewport' => 25, // Raised: mobile-first is now a baseline requirement'no_analytics' => 22, // Raised: GA/GTM pitch — easy and recurring'no_whatsapp' => 18, // Raised: primary ES/LATAM contact channel// Señales NEGATIVAS (filtros) - RESTAN puntos'too_many_reviews' => -10,'high_rating' => -5,Idempotencia en la facturación
Checkout y cancelación usan la cabecera X-Idempotency-Key, y los cambios de créditos pasan por un servicio de libro mayor (ledger). Un reintento no puede cobrar ni descontar dos veces.
Una única fuente de verdad para los planes
Planificado, no hecho: un archivo de configuración con una interfaz de repositorio y una instantánea de derechos por suscripción.
Hecho y planificado
Separado a propósito: lo que existe hoy y lo que todavía no.
Hecho
- Autenticación con Sanctum.
- Búsqueda geolocalizada con Nominatim y Google Places.
- Filtro de elegibilidad y relevancia en dos capas.
- Puntuación por señales V1/V2, sub-puntuaciones y etiquetas.
- Auditoría web con heurísticas de SEO, SEM y anuncios en redes.
- Encaje de perfil, motivo de contacto y recomendación de servicios.
- Facturación con Stripe y webhooks.
- CI y puertas de análisis estático.
- Cambio de nombre a Opplook y envoltorio uniforme de errores por código.
Planificado, sin hacer
- Workspaces y equipos.
- Planes de suscripción.
- Guiones y mensajes con IA (contrato LlmClient).
- Informes white-label para compartir, con seguimiento de aperturas.
- Pipeline.
- Radar semanal de zonas.
- Redis, Horizon y Sentry.
- Nueva aplicación de frontend.
- Retirar el código heredado de créditos, desbloqueo y exportación.
Esto es lo que hay detrás. Ahora, el producto.
Mira cómo se ve Opplook o pide acceso anticipado.