/diego/reseller EscríbemeWrite to me
AI Engineer · visión + LLM · reventa de segunda manoAI Engineer · vision + LLM · second-hand reselling
github.com/diegollr98-design/resellermaster

Construí el sistema que impide a una IA mentir sobre un producto.

I built the system that stops an AI from lying about a product.

RESELLERMASTER coge un lote de fotos mezcladas de ropa y objetos de segunda mano, las agrupa por producto y produce la ficha lista para copiar-pegar en Wallapop y Vinted (título, marca, talla, estado, precio) sin afirmar ni un atributo que no sea legible en una foto. El modo de fallo de este proyecto no es que el código pete: es que el pipeline invente «Nike, talla M, algodón, 25€» con total fluidez y yo lo publique. Una ficha mala no es un bug: es una venta perdida y reputación quemada. Empecé sin saber programar; lo levanté solo, con IA.

RESELLERMASTER takes a batch of mixed photos of second-hand clothes and objects, groups them by product and produces the listing ready to copy-paste into Wallapop and Vinted, title, brand, size, condition, price, without asserting a single attribute that isn't legible in a photo. This project's failure mode isn't the code crashing: it's the pipeline fluently inventing "Nike, size M, cotton, €25" and me publishing it. A bad listing isn't a bug: it's a lost sale and burned reputation. I started not knowing how to code; I built it solo, with AI.

App local de un solo usuario. No automatiza la publicación (va contra los términos de Wallapop/Vinted): llega hasta «copiar y pegar en 2 clics», a propósito.Local single-user app. It does not automate publishing (against Wallapop/Vinted terms): it goes as far as "copy & paste in 2 clicks", on purpose.

s
un export real cronometrado (n=1) · a mano ~285 s, estimado por un panelone real timed export (n=1) · by hand ~285 s, estimated by a panel
afirmaciones con confianza alta sin un píxel que las respalde · 33 fotos reales (7 productos), medido con la API contra el mapa de legibilidadhigh-confidence claims with no pixel to back them · 33 real photos (7 products), measured against the API using the legibility map
cts
coste medio de extracción por producto · 0 al reprocesar (caché)avg extraction cost per product · 0 on reprocess (cache)
fases cerradas · agrupar → extraer → export → precio → finanzasphases closed · group → extract → export → price → finances
RecorridoWalkthrough

Del montón de fotos a la ficha, en 2 clics

From a pile of photos to the listing, in 2 clicks

El producto es visual, así que la prueba también. Un vídeo del pipeline real: ingesta del lote → curar la agrupación → ficha con cada campo junto a su recorte → export copy-paste por plataforma → finanzas. El clímax es el export cronometrado.

The product is visual, so is the proof. A video of the real pipeline: batch ingest → curate the grouping → listing with each field next to its crop → per-platform copy-paste export → finances. The climax is the timed export.

vista previa · corte heropreview · hero cut

Guion: ingesta → cremallera con pestillo (curar) → ficha con badges 📷/🧠 → export por plataforma → «5. Finanzas» (Subido → Vendido → beneficio → Excel). El export real se cronometró una vez en ~210 s (n=1; a mano ~285 s, estimado).Script: ingest → latching zipper (curate) → listing with 📷/🧠 badges → per-platform export → "5. Finances" (Uploaded → Sold → profit → Excel). The real export was timed once at ~210 s (n=1; by hand ~285 s, estimated).

Ficha · producto 3Listing · product 3 representación de la pantalla realrepresentation of the real screen
MarcaReebok📷 leídoread
TallaXXL📷 leídoread
TipoTypesudaderahoodie🧠 inferidoinferred
Colorgrisgrey🧠 inferidoinferred
MaterialMaterial, (etiqueta no fotografiada), (label not photographed)null
EstadoConditionMuy buenoVery good✓ lo confirma Diego✓ Diego confirms
PrecioPrice20 € mediana de 15 parecidosmedian of 15 similarcomparablescomparables
Cada campo lleva su recorte de la foto al lado. La app PROPONE; Diego CONFIRMA con el píxel delante.Each field shows its photo crop alongside. The app PROPOSES; Diego CONFIRMS with the pixel in front of him.

Esto es lo que ningún otro proyecto mío tiene: una ficha donde cada dato dice de dónde salió. Un badge 📷 leído significa que el valor está contenido en el texto legible del recorte citado. Un 🧠 inferido es la mejor estimación del modelo, marcada como tal y con confianza baja.

El material sale vacío porque la etiqueta no está en ninguna foto, y eso es un acierto, no un hueco: un campo vacío se recupera; una talla inventada es una devolución.

This is what no other project of mine has: a listing where every value says where it came from. A 📷 read badge means the value is contained in the legible text of the cited crop. A 🧠 inferred one is the model's best guess, flagged as such with low confidence.

Material is left empty because the label isn't in any photo, and that's a win, not a gap: an empty field is recoverable; an invented size is a return.

📷 en la fotoin the photo 🧠 inferidoinferred confirmadoconfirmed null no legiblenot legible
El ahorro a escalaSaving at scale

La app no gana en un producto. Gana en el lote.

The app doesn't win on one product. It wins on the batch.

A mano, cada producto es trabajo íntegro que se repite: fotografiar, teclear, categorizar, tasar, pegar. Con la app, el agrupado, la extracción y la confirmación se hacen en bloque para todo el lote, así que la ventaja no es fija: compone con cada producto que añades.

By hand, each product is full work repeated: shoot, type, categorize, price, paste. With the app, grouping, extraction and confirmation happen in bulk for the whole batch, so the advantage isn't fixed: it compounds with every product you add.

productos en el loteproducts in the batch 10 productosproducts
A manoBy hand1 h 10 min
Con la appWith the app37 min
Ahorro proyectado: ~33 min en 10 productos. Projected saving: ~33 min across 10 products. −47%menos tiempoless time
⚠ ESTIMACIÓN RAZONADA (persona media), no cronometrada. A mano ≈ 7 min/producto y todo se repite: separar las fotos · buscar referencia de precio en Wallapop/Vinted/Google · pensar título+descripción · categoría+atributos · subir a las 2 plataformas · registrar en Excel. Con la app: ~2 min fijos de montar el lote (ingesta + «extraer todo»: 2 clics para N productos) + ~3,5 min por ficha (revisar con el recorte + export copy-paste; ≈ el export medido, n=1). Por eso el % crece con el lote: lo fijo se amortiza. Cifras para una persona media (Diego va más rápido); el único dato MEDIDO es el export (~210 s). El imbatible sigue siendo cronometrar un lote real. ⚠ REASONED ESTIMATE (average person), not timed. By hand ≈ 7 min/product and everything repeats: separate the photos · look up a price reference on Wallapop/Vinted/Google · think up title+description · category+attributes · upload to both platforms · log it in Excel. With the app: ~2 min fixed to set up the batch (ingest + "extract all": 2 clicks for N products) + ~3.5 min per listing (review with the crop + copy-paste export; ≈ the measured export, n=1). That's why the % grows with the batch: the fixed cost amortizes. Figures for an average person (Diego is faster); the only MEASURED datum is the export (~210 s). The unbeatable one is still to time a real batch.
14 → 2

clics para extraer el loteclicks to extract the batch

«Extraer TODO el lote» cuesta 2 clics, sea N=1 o N=30. Antes era 2 por producto. Hecho contado, no estimado."Extract the WHOLE batch" costs 2 clicks, whether N=1 or N=30. It used to be 2 per product. A counted fact, not an estimate.

~1 €

coste de 30 productoscost of 30 products

3,4 cts/producto de extracción · 0 al reprocesar (caché por hash de imagen: cada llamada se paga una vez).3.4 cts/product for extraction · 0 on reprocess (cache by image hash: each call is paid once).

×N

cómo componehow it compounds

A mano cada producto se repite entero; en la app agrupar / extraer / confirmar se amortizan sobre todo el lote.By hand each product repeats in full; in the app grouping / extracting / confirming amortize across the whole batch.

Flagship · el corazón del proyectoFlagship · the heart of the project

El Loop de Verdad: la app no AFIRMA, PROPONE

The Truth Loop: the app doesn't ASSERT, it PROPOSES

La app está diseñada para que yo confíe y copie rápido, y esa confianza es exactamente lo que convierte una alucinación en una venta perdida. Así que la honestidad no es un eslogan: está incrustada en la arquitectura. Quien AFIRMA es el humano al confirmar; la máquina sólo propone y enseña el píxel.

The app is built so I trust it and copy fast, and that trust is exactly what turns a hallucination into a lost sale. So honesty isn't a slogan: it's baked into the architecture. The one who ASSERTS is the human on confirm; the machine only proposes and shows the pixel.

# la garantía no la da el prompt — la fuerza un `if` # core/extract.py :: _construir_campo_desde_sintesis valor = "Reebok" cita = crop("IMG_0421.jpg", region_etiqueta) if valor.lower() in texto_legible(cita).lower(): fuente = "foto" # está en el píxel confianza = "media" else: fuente = "inferido" # NO se ve → se marca confianza = "baja" # extender una lectura ("Reebok" → "Reebok # Classic 100% algodón") es INEXPRESABLE: # el valor extendido ya no está en la cita.
# the guarantee isn't the prompt: an `if` forces it # core/extract.py :: _build_field_from_synthesis value = "Reebok" cite = crop("IMG_0421.jpg", label_region) if value.lower() in legible_text(cite).lower(): source = "photo" # it's in the pixel confidence = "medium" else: source = "inferred" # NOT visible → flagged confidence = "low" # extending a reading ("Reebok" → "Reebok # Classic 100% cotton") is UNEXPRESSABLE: # the extended value is no longer in the cite.

Cada campo lleva procedencia obligatoria: valor · fuente · evidencia · confianza. Un fuente=foto sin evidencia no es un dato, es un bug.

Un panel adversarial Opus (mi agente listing-audit) cazó justo esto: el if sólo validaba que la cita existiera, no que el valor estuviera en ese píxel: el modelo podía extender una lectura y colarla como leída. Veredicto BLOQUEANTE, corregido con guarda + test.

Una garantía prometida en prosa no está garantizada hasta que un `if` la fuerza.

Every field carries mandatory provenance: value · source · evidence · confidence. A source=photo without evidence isn't a datum, it's a bug.

An adversarial Opus panel (my listing-audit agent) caught exactly this: the if only checked the citation existed, not that the value was in that pixel: the model could extend a reading and pass it as read. BLOCKING verdict, fixed with a guard + test.

A guarantee promised in prose isn't guaranteed until an `if` enforces it.

El precio nunca sale del LLMPrice never comes from the LLM

El precio no es un atributo del producto: es una observación del mercado. Sale de comparables reales con su URL (la mediana de N parecidos, que puedo abrir y comprobar) o es None + motivo. Nunca un número plausible sin fuente. Límite honesto que la propia app declara en su UI: los anuncios publican lo que la gente PIDE, no por cuánto VENDIÓ (los precios de venta no son públicos).

Price is not a product attribute: it's a market observation. It comes from real comparables with their URL (the median of N similar items I can open and check) or it's None + reason. Never a plausible number without a source. An honest limit the app states in its own UI: listings publish what people ASK, not what they SOLD for (sale prices aren't public).

COSTURA 01 · ExtractorEngine

Un solo punto de llamadaOne call point

Toda llamada a cualquier proveedor (Claude/OCR/local) pasa por un módulo: contabiliza el coste por producto y cachea por hash de imagen. El proveedor es una decisión reversible, no una dependencia esparcida.Every call to any provider (Claude/OCR/local) goes through one module: it accounts cost per product and caches by image hash. The provider is a reversible decision, not a scattered dependency.

COSTURA 02 · PriceEngine

Precio desde comparablesPrice from comparables

Lee la búsqueda pública por texto, coge ~15 parecidos y devuelve mediana + rango + URLs. Sin n≥5 → None + motivo. Leer ≠ publicar: volumen doméstico, sin herramientas de evasión.Reads the public text search, takes ~15 similar items and returns median + range + URLs. Without n≥5 → None + reason. Reading ≠ publishing: domestic volume, no anti-detection tooling.

COSTURA 03 · ListingSchema

Los campos, en un sitioFields in one place

Los campos obligatorios de cada plataforma × categoría viven en un esquema declarativo. El LLM RELLENA un esquema; no inventa campos ni olvida los obligatorios. Añadir una categoría es añadir datos, no código.Each platform × category's required fields live in a declarative schema. The LLM FILLS a schema; it doesn't invent fields or forget required ones. Adding a category is adding data, not code.

Lo que decidí NO construirWhat I decided NOT to build

Decisiones medidas, no intuidas

Measured decisions, not guessed

El activo que no se puede fingir no es el código: es el juicio. Antes de pagar una API o meter una dependencia, mido la alternativa gratuita sobre datos reales. Matar una hipótesis con un dato ( incluso cuando el dato va en mi contra ) es la señal de un proceso serio.

The asset you can't fake isn't the code: it's judgment. Before paying for an API or adding a dependency, I measure the free alternative on real data. Killing a hypothesis with data ( even when the data goes against me ) is the signature of a serious process.

IdeaIdea Por qué tentabaWhy it tempted Qué medíWhat I measured VeredictoVerdict
Búsqueda por imagen / LensImage search / Lensidentificar el producto por su fotoidentify the product from its photo «sube la foto y te dice qué es»"upload the photo and it tells you what it is" todas exigen URL pública; app local sin hostingall require a public URL; local app, no hosting Descartada · 4 agentesRuled out · 4 agents
CLIP · match visualvisual matchagrupar fotos del mismo producto por similitudgroup same-product photos by similarity embeddings gratis, sin APIfree embeddings, no API 0.90 de similitud entre dos sudaderas DISTINTAS0.90 similarity between two DIFFERENT hoodies [INC-004]
OCR · clasificar tipo de fotoclassify photo typedetectar etiqueta/metro/plano por su textodetect label/tape/wide-shot by its text etiqueta y metro SON textolabel and tape ARE text los caracteres se solapan por completocharacter counts overlap completely [INC-007]
VLM localLocal VLMcorrer un modelo de visión en mi portátilrun a vision model on my laptop coste 0, sin depender de nadiezero cost, no dependency RTX 3050 · 4 GB VRAM → no entra→ won't fit Medido, no intuidoMeasured, not guessed
Automatizar la publicaciónAutomate publishingSelenium sobre los formularios de Wallapop/VintedSelenium over Wallapop/Vinted forms el paso final «gratis»the "free" final step contra sus términos → baneo de la cuenta que ES el negocioagainst their terms → ban on the account that IS the business Descartada a propósitoRuled out on purpose
Umbral de agrupación 15 s15 s grouping thresholdcuándo dos fotos son productos distintoswhen two photos are different products barrido completo 5–30 s sobre 33 fotos realesfull 5–30 s sweep over 33 real photos zona segura 1–23 s → 0 fusiones; acantilado a 24 ssafe zone 1–23 s → 0 merges; cliff at 24 s En usoIn use
El pipelineThe pipeline

Cinco etapas, un solo estado en disco

Five stages, one state on disk

El flujo es un flywheel de cinco etapas. El estado del curado se escribe en disco, no sólo en memoria: un rerun de Streamlit, un crash o un cierre de pestaña no pueden costarme dos horas de trabajo.

The flow is a five-stage flywheel. Curation state is written to disk, not just memory: a Streamlit rerun, a crash or a closed tab can't cost me two hours of work.

pasa el ratón por una etapa para resaltarlahover a stage to highlight it
1 INGERIRINGEST

Fotos + EXIFPhotos + EXIF

Sin fecha (WhatsApp la borra) la señal primaria no existe → la app avisa en la ingesta.No date (WhatsApp strips it) → the primary signal is gone → the app warns on ingest.

2 AGRUPARGROUP

La cremalleraThe zipper

Propone clusters por hueco temporal; Diego confirma. Fusionar dos productos es INEXPRESABLE.Proposes clusters by time gap; Diego confirms. Merging two products is UNEXPRESSABLE.

3 EXTRAEREXTRACT

Schema + procedenciaSchema + provenance

OCR localiza el texto → recorte → el VLM lee. Cada campo con su badge 📷/🧠.OCR locates text → crop → the VLM reads. Every field with its 📷/🧠 badge.

4 TASARPRICE

ComparablesComparables

Mediana de parecidos con URL, editable. Sin n≥5 → None + motivo.Median of similar items with URLs, editable. Without n≥5 → None + reason.

5 EXPORTAREXPORT

Copy-paste por plataformaPer-platform copy-paste

Literales exactos por plataforma, orden del formulario de cada una. Era el ~66% de mi tiempo.Exact platform literals, each one's form order. It was ~66% of my time.

El reloj puede PARTIR, pero no CONFIRMARThe clock can SPLIT, but not CONFIRM

La agrupación no optimiza acierto: optimiza no-fusionar. Partir un producto de más cuesta 5 segundos; fusionar dos mete una foto del producto A en la ficha del B, y eso no lo caza nadie. Por eso una pausa larga puede partir un grupo, pero la ausencia de pausa no confirma nada, y ningún grupo sale con confianza alta desde el reloj. La unidad de la UI es la frontera, no la foto: mezclar dos productos lejanos es inexpresable, no sólo desaconsejado.

Grouping doesn't optimize accuracy: it optimizes not-merging. Over-splitting a product costs 5 seconds; merging two puts a photo of product A into product B's listing, and nobody catches that. So a long pause may split a group, but the absence of a pause confirms nothing, and no group ships with high confidence from the clock. The UI's unit is the boundary, not the photo: mixing two distant products is unexpressable, not merely discouraged.

Cómo trabajo con IAHow I work with AI

Validación adversarial multi-agente

Adversarial multi-agent validation

No le pido a la IA una respuesta. Un orquestador (Opus) delega la construcción en agentes Sonnet en paralelo, y luego un panel escéptico ataca lo construido: bug-hunter busca la causa raíz sin contexto, listing-audit intenta pillar al pipeline mintiendo contra las fotos reales, flow-qa mide la velocidad. El orquestador re-deriva cada veredicto ejecutando, no por informe. Yo juzgo.

I don't ask the AI for an answer. An orchestrator (Opus) delegates building to parallel Sonnet agents, then a skeptical panel attacks what was built: bug-hunter finds the root cause with no context, listing-audit tries to catch the pipeline lying against the real photos, flow-qa measures speed. The orchestrator re-derives every verdict by executing, not by report. I judge.

el orquestador re-deriva el veredicto EJECUTANDO · no por informe the orchestrator re-derives the verdict by EXECUTING · not by report PANEL ADVERSARIAL · intentan pillar al pipeline mintiendo ADVERSARIAL PANEL · they try to catch the pipeline lying CONSTRUIRBUILD Sonnet en paraleloparallel Sonnet /eval golden set · alucinacióngolden set · hallucination bug-hunter causa raíz, sin contextoroot cause, no context listing-audit refuta las afirmacionesrefutes the claims flow-qa segundos por productoseconds per product /seed-review agente ciegoblind agent LA FICHATHE LISTING bajo ataqueunder attack DIEGO · juzgaDIEGO · judges el píxel delantethe pixel in front PUBLICAPUBLISHES copy-pastecopy-paste AL LEDGERTO THE LEDGER → regla vía /optimize→ rule via /optimize

Pellizca para acercar y leer el detalle ↔Pinch to zoom in and read the detail ↔

Una ficha sobrevive sólo si resiste el ataque. El juicio final es mío, con el píxel delante: es la única capa que verifica verdad, no sólo legibilidad.

A listing survives only if it withstands the attack. The final judgment is mine, with the pixel in front: the only layer that verifies truth, not just legibility.

Verificación por ejecuciónVerification by execution

Ningún «el subagente reportó que OK» cierra nada. Corro yo pytest / /eval / la app y pego la salida REAL en el prompt del siguiente agente. Un informe no es evidencia; una salida sí.No "the subagent reported OK" closes anything. I run pytest / /eval / the app myself and paste the REAL output into the next agent's prompt. A report isn't evidence; output is.

# esta misma semana: el /seed-review de MI plan de cierre $ /seed-review cierre-ultra-review.md agente CIEGO // no ve el plan → deriva el objetivo desde la fuente 2 críticos // en paralelo, mandato: REFUTAR ciego el plan audita CÓDIGO con 900 tests verdes… pero la métrica-norte «segundos-hasta-publicar» NUNCA se midió en 5 fases → es un PROXY, no el objetivo [INC-016] # reordené: MEDIR primero → ~210 s con la app vs ~285 a mano # y midiéndolo apareció un bug real: [INC-031] 400 de esquema en TODA re-extracción // 904 tests // verdes no lo vieron: mockean el motor y // nunca mandan el json_schema a la API real
# this very week: the /seed-review of MY closing plan $ /seed-review closing-ultra-review.md BLIND agent // never sees the plan → derives the goal from the source 2 critics // in parallel, mandate: REFUTE blind the plan audits CODE with 900 green tests… but the north metric "seconds-to-publish" was NEVER measured in 5 phases → it's a PROXY, not the goal [INC-016] # I reordered: MEASURE first → ~210 s with the app vs ~285 by hand # and measuring it surfaced a real bug: [INC-031] schema 400 on EVERY re-extraction // 904 green // tests didn't see it: they mock the engine and // never send the json_schema to the real API

El panel cazó que mi propio plan de cierre probaba un proxy: auditar código verde en vez de medir la métrica primaria del proyecto. Reordené, medir primero, y el número que corona el proyecto (~210 s) apareció ahí, junto a un bug que 904 tests verdes no veían. El rigor es cuestionar tu propio plan, no defenderlo.

The panel caught that my own closing plan was testing a proxy: auditing green code instead of measuring the project's primary metric. I reordered, measure first, and the number that crowns the project (~210 s) showed up there, next to a bug 904 green tests couldn't see. Rigor is questioning your own plan, not defending it.

El resto del método —la refutación asimétrica, el agente ciego que audita mis planes, el ledger que convierte un fallo en regla— es el mismo en los cinco sistemas y está contado una vez: en el índice.The rest of the method —asymmetric refutation, the blind agent auditing my own plans, the ledger that turns a failure into a rule— is the same across all five systems and is told once: on the index.

Disponible para trabajarAvailable for work

Cojo un problema difuso, lo descompongo y orquesto IA para construir cada pieza, sin perder el rigor.

I take a fuzzy problem, break it down and orchestrate AI to build every piece, without losing the rigor.

Si buscas a alguien para applied-AI, automatización o forward-deployed engineering, que construye sistemas para que la IA no se engañe a sí misma, hablemos.

If you're looking for someone in applied-AI, automation or forward-deployed engineering who builds systems so the AI can't fool itself, let's talk.

RESELLERMASTER · Diego · 2026 App local · construido con IA · sin formación técnica previaLocal app · built with AI · no prior technical background