Skip to content
vp.Ven Petrov
WorkWritingAboutAt a glance
← Projects & notes
Capture CRMProject story · 3 min read

Connecting classified listings to contacts in a small CRM

A Chrome extension and Next.js dashboard bring classified listings, platform profiles and contact history together. Saving a listing again can add the person who was missing from an earlier import.

Ven PetrovLocal prototype

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 response

The 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
Local repository and code review

capture-crm-wallapop-milanuncios: src/lib/schema.ts, phone.ts, src/app/api/extension/save/route.ts and extension/background/service-worker.ts.

Documented architecture

CLAUDE.md describes the background-worker message flow, dashboard sessions, extension API-key access and status annotations.

Scope of result

Implementation inspected; no capture-volume, sales-conversion or production-reliability metric was established.

On this page
The second save can contain the important partBring contact status back to the browsing pageThe boundary of this prototypeSources & checks
Next.jsReactChrome extensionData modeling

Connected to this

All writing
Sociedades Inactivas enquiries · Technical note

Saving an enquiry before trying to send its email

Transfutur ERP · Experience

ERP for transport and logistics

Ven PetrovSoftware engineer · Barcelona
GitHub ↗LinkedIn ↗Email ↗

Find a project or a note

17 results

Transfutur ERP · ExperienceERP for transport and logisticsAs the sole engineer, I built an ERP from zero for transport and logistics. It supported roughly €5–6 million in annual invoicing across two offices, and I kept it running for about a year.Financial review lab · Project storyExploring a financial report with Python and FastAPII started with one income change, then opened up the same 54-page report: products, regions, cash flow, core adjustments and a dataset whose figures lead back to the source.Crude-oil narrative tracker · Project storyCrude-oil research from global news and market dataI wanted help following global events that could matter for crude oil. The prototype combines news research with calculated market and position values, while keeping recent assessments separate from the longer numeric history.MNQ time-series research · ExperimentTime-series prediction experimentsAn exploration of high-resolution market data: choosing what to acquire, checking its limits, comparing time intervals and testing whether a prediction survived later data and the cost of acting on it.NQ/MNQ backtest engine · Technical noteA missing closing bar left the simulator’s short position openThe strategy reset for a new day while a shared counter still counted yesterday’s short. A small replay shows why closing the position has to happen before clearing its state.Small-cap edge research · Project storyTurning small-cap trading ideas into repeatable researchI asked Claude Code for a fresh research system that could test named price patterns, compare later periods and carry its methodology into new sessions. One candidate shows how features, entry timing and costs fit together.Gadgeon Europe applications · ExperienceConnected-device dashboards and mobile apps at Gadgeon EuropeI worked on Ionic and Angular applications that brought several APIs into dashboards for energy monitoring and connected-device control, with releases and maintenance across multiple client versions.ctxforge · Project storyctxforge: discovering what to build before codingA context discovery framework that asks an LLM to act like a senior engineering interviewer: understand the idea, uncover requirements and agree the scope before an agent starts building.Sociedades Inactivas enquiries · Technical noteSaving an enquiry before trying to send its emailOn the Sociedades Inactivas catalogue, a visitor can ask about a company or send a general enquiry. The form separates saving that request from delivering its notification, including when the visitor retries.Astro local-services website · Project storyA local-services site assembled from city, service and FAQ recordsThe Astro site describes flooring and other local services across different cities. Shared templates check availability, read the matching content and select one FAQ set for both visitors and structured data.3D Spaces website migration · Project storyMoving 3D Spaces to Next.js while its email stayed on the old serverI rebuilt the virtual-tour website with AI coding collaborators, preserving its content and URLs. The cutover also had to protect the mailbox, enquiry flow and existing analytics history.Capture CRM · Project storyConnecting classified listings to contacts in a small CRMA Chrome extension and Next.js dashboard bring classified listings, platform profiles and contact history together. Saving a listing again can add the person who was missing from an earlier import.Product-description comparison bench · ExperimentComparing one model call with separate vision and writing stepsA small product-description tool lets me inspect the visual analysis before the copy is written, change the model used for each step and compare request measurements.Between Us · Project storyBetween Us: why I kept the first take and widened the shotI compared dialogue takes, asked for wider shots and more physical acting, then changed the workflow when its studio limited the scenes I wanted to make.Restaurant-service voice agent · ExperimentDesigning a restaurant-service call around the real offerI worked through a Spanish outbound agent prompt, corrected a free visit to the actual paid service and compared how two drafts handled transfers and bookings.Vapi and Telnyx voice integration · Technical noteThe Vapi agent could speak, but it could not hear me clearlyMy Telnyx number connected to the assistant, but the caller audio was distorted. Three codec tests recovered the connection without fixing that original problem.Warloop: Echoes of Solmara · Project storyWarloop: turning generated character art into a playable prototypeGood-looking character images became tiny figures, a backwards walk and misplaced bosses in the game. I worked through the gap between generated art and playable behavior.
esc to close · Tab to move through results