Capture CRM connects a Chrome extension to a Next.js dashboard for a Spanish flooring-materials company. Its documented workflow is to collect professional listings from Wallapop and Milanuncios, associate them with contacts and keep track of later interactions.
A listing is not the same thing as a person. One person can have a platform profile, several ads and an interaction history. The database therefore stores listings, profiles, contacts and interactions as different records.
That distinction becomes useful when information arrives in pieces.
The second save can contain the important part
A CSV import can create an ad containing a URL and description without a phone number. The ad exists, but its contactId is empty. Later, the extension can capture the same URL with a name or number.
The save route has a branch for that case. It links the existing ad to a found or newly created contact and fills empty listing fields. Values already stored are preserved. If the ad already has a contact, it returns that existing relationship.
This is an illustration of the implemented states, not a claimed customer record:
| First import | Later capture | Save result |
|---|---|---|
| Listing URL, no contact | Same URL with contact details | Enrich the listing and link a contact |
| Listing already linked | Same URL again | Return the existing relationship |
| New listing | Phone or existing profile match | Try to reuse the corresponding contact |
The phone normalizer removes spaces and common formatting, strips +34 or 0034, then accepts nine digits beginning with 6, 7, 8 or 9. That helps the same Spanish number match across formats. It cannot determine whether a number is shared or has changed owner.
Bring contact status back to the browsing page
The extension does more than send a listing to the dashboard. It can ask which visible listing URLs are known and show their linked contact status while the operator browses. The dashboard's workflow includes states for calling, messaging, a conversation and a shop visit.
New cards can arrive through infinite scrolling. A MutationObserver, a browser mechanism that watches page changes, triggers processing for those added cards. Platform-specific selectors describe where the extension finds the listing data.
The requests leave through the extension's background worker:
classified page -> content-script message
-> extension background worker
-> CRM API -> relationship/status responseThe content script reads the page; the worker calls the CRM on its different web origin and returns the result. Its asynchronous message channel stays open until the reply is ready. Extension requests use the configured API key, while the dashboard has its own signed-in session.
The boundary of this prototype
The capture and enrichment paths are implemented, but saving currently involves several checks followed by inserts rather than one atomic upsert. Two concurrent saves can therefore race. Profile matching can also use a URL suffix, which needs collision tests across platforms.
Those are concrete next checks for the relationship model. The useful result already present is that information gathered later can complete an existing listing without treating every repeat capture as disposable duplication. No sales or capture-volume result has been measured here.
Project sources & checks
capture-crm-wallapop-milanuncios: src/lib/schema.ts, phone.ts, src/app/api/extension/save/route.ts and extension/background/service-worker.ts.
CLAUDE.md describes the background-worker message flow, dashboard sessions, extension API-key access and status annotations.
Implementation inspected; no capture-volume, sales-conversion or production-reliability metric was established.