ECXM OPS cubre la cadena completa de un negocio de dropshipping COD, de espiar a la competencia a cerrar el mes, en un solo dashboard y una CLI de 35 comandos. El modo de fallo no es que Streamlit pete: es que el pivot afirme con total fluidez una utilidad neta falsa. Un decimal mal atribuido es el sueldo de otra persona.
ECXM OPS covers the full chain of a COD dropshipping business, from spying on competitors to closing the month, in one dashboard and a 35-command CLI. The failure mode isn't Streamlit crashing: it's the pivot fluently asserting a false net profit. One misattributed decimal is somebody else's pay.
En uso diario en mi propio negocio desde mayo de 2026, con 6.286 anuncios de la competencia indexados. Construido en solitario, con IA.In daily use in my own business since May 2026, with 6,286 competitor ads indexed. Built solo, with AI.
No son siete scripts sueltos: es una sola cadena donde la salida de cada etapa es la entrada de la siguiente, y donde el estado vive en archivos que las demás etapas saben leer. Pulsa cualquier estación para ver qué hace, qué cuesta y con qué se sincroniza.
These aren't seven loose scripts: it's a single chain where each stage's output is the next one's input, and where state lives in files the other stages know how to read. Click any station to see what it does, what it costs and what it syncs with.
Guion: escaneo de la Ads Library → producto al LAB (precios + copy + títulos) → réplica de la landing en $0 → creativos → control diario → cierre de mes → la tabla que dice qué producto es winner.Script: Ads Library scan → product into the LAB (pricing + copy + titles) → landing replica at $0 → creatives → daily control → month close → the table that says which product is a winner.
Estas 7 etapas del ciclo del producto viven en 6 pestañas del dashboard (cerrar y vender comparten FINANZAS). Hay 3 más de soporte (FEEDBACK, ACADEMIA y ROADMAP, que renderiza el mapa de arquitectura del propio dashboard) 9 en total.These 7 product-cycle stages live in 6 dashboard tabs (sell and close share FINANCE). There are 3 more support tabs (FEEDBACK, ACADEMIA and ROADMAP, which renders the dashboard's own architecture map) 9 in total.
El contrato del equipo tiene una cláusula corta: un producto es winner si cierra el mes con utilidad neta positiva, y entonces el reparto pasa de 50/50 a 40/60 a favor del colaborador. Toda la maquinaria de abajo existe para producir ese número. El dashboard está hecho para que yo confíe y cierre rápido: esa confianza es exactamente lo que convierte un error de atribución en dinero mal repartido.
The team contract has one short clause: a product is a winner if it closes the month with positive net profit, and then the split moves from 50/50 to 40/60 in the collaborator's favour. All the machinery below exists to produce that number. The dashboard is built so I trust it and close fast: that trust is exactly what turns an attribution error into money paid out wrong.
Pellizca para acercar y leer el detalle ↔Pinch to zoom in and read the detail ↔
La convención de nombres de campaña en Meta ([NOMBRE]_[PRODUCTO]_[FECHA]) no es cosmética: es lo que permite que el gasto publicitario baje hasta el producto correcto. Si una campaña no la respeta, el sistema lo marca, sin ella la atribución falla y la liquidación se reparte mal.
The Meta campaign naming convention ([NAME]_[PRODUCT]_[DATE]) isn't cosmetic: it's what lets ad spend flow down to the right product. If a campaign breaks it, the system flags it, without it attribution fails and the payout is split wrong.
| FuenteSource | Qué esWhat it is | Regla duraHard rule | UsoUse |
|---|---|---|---|
| LIMPIO | ventas, estatus y flete del export de Dropisales, status and shipping from the Dropi export | solo los 4 estatus finales cuentan para el mes; un pedido en tránsito no tiene resultado todavíaonly the 4 final statuses count for the month; an in-transit order has no outcome yet | basebase |
| FACTURA (OCR) | costes, servicios y publicidad leídos de los PDFscosts, services and ad spend read from the PDFs | es lo que se pagó de verdad; un cobro con estado «Error» no cuentait's what was actually charged; a payment in "Error" state doesn't count | caja · liquidacióncash · payout |
| CSV Ads | impresiones, visitas y compras de Metaimpressions, visits and purchases from Meta | nunca reemplaza a la factura como coste real: no trae ni desglose por campaña ni estado del cobronever replaces the invoice as real cost: it carries neither per-campaign breakdown nor payment state | rendimiento · CPAperformance · CPA |
| PRORRATEOALLOCATION | servicios repartidos entre productosservices split across products | es una estimación y se etiqueta como tal; jamás se presenta como coste directo del productoit's an estimate and is labelled as one; never presented as a direct product cost | estimadoestimated |
Abstenerse cuesta 5 segundos; una cifra falsa cuesta una liquidación. Ante un dato incompleto (divisa desconocida, factura sin desglose, mes sin cerrar) el sistema devuelve None y avisa en rojo. Nunca un número plausible sin fuente.
Ese bug era real y estaba vivo: la conversión silenciosa a tasa 1.0 hacía pasar una factura en dólares por pesos colombianos. No lo cazó ningún test verde: lo cazó preguntarle al código de dónde sale este número.
Por eso cada dato lleva su procedencia y las fuentes no se sustituyen entre sí. Mezclar la factura (lo que pagué) con el CSV de Meta (cómo rindió) fue un bug de verdad, no una discusión teórica. Hoy es una regla escrita.
Abstaining costs 5 seconds; a false figure costs a payout. Faced with incomplete data (unknown currency, invoice with no breakdown, month not closed) the system returns None and warns in red. Never a plausible number with no source.
That bug was real and live: the silent 1.0-rate conversion passed a dollar invoice off as Colombian pesos. No green test caught it: what caught it was asking the code where does this number come from.
That's why every value carries its provenance and the sources never substitute for one another. Mixing the invoice (what I paid) with the Meta CSV (how it performed) was a genuine bug, not a theoretical debate. Today it's a written rule.
$ python tests/golden_finanzas/eval_finanzas.py check-all [check] ENERO 2026 — VEREDICTO: PASA (5 productos, igualdad exacta) [check] MARZO 2026 — VEREDICTO: PASA (4 productos, igualdad exacta) [check] ABRIL 2026 — VEREDICTO: PASA (3 productos, igualdad exacta) # el gate corre las FUNCIONES REALES del cierre — pivot.calcular_neta_mes() # y pivot.utilidad_mes() — contra tres meses cerrados y verificados a mano. # Sin reimplementar la lógica: si el golden y producción divergen, es un bug # del golden, no del negocio. # # Igualdad EXACTA, no ±tolerancia: el OCR queda fuera de este camino, así que # dos corridas sobre los mismos inputs dan el mismo número al peso. Una # tolerancia del 0,5 % enmascararía una regresión real. # # Si BLOQUEA, el cambio no se cierra. No es un aviso: es un no.
$ python tests/golden_finanzas/eval_finanzas.py check-all [check] JANUARY 2026 — VERDICT: PASS (5 products, exact equality) [check] MARCH 2026 — VERDICT: PASS (4 products, exact equality) [check] APRIL 2026 — VERDICT: PASS (3 products, exact equality) # the gate runs the REAL closing functions — pivot.calcular_neta_mes() and # pivot.utilidad_mes() — against three closed, hand-verified months. # No reimplemented logic: if golden and production diverge, it's a bug in # the golden, not in the business. # # EXACT equality, not ±tolerance: OCR is out of this path, so two runs over # the same inputs give the same number to the peso. A 0.5 % tolerance would # mask a real regression. # # If it BLOCKS, the change does not ship. It isn't a warning: it's a no.
Ningún agente puede verificar que una factura sea real o que un flete sea correcto. Lo que sí es verificable por ejecución es que la cifra es reproducible desde inputs congelados y que cada número dice de dónde sale. El golden comprueba aritmética y trazabilidad, no verdad de negocio: eso lo firma una persona mirando el mes real. El pipe propone; yo confirmo. Y el ground-truth del golden lo fijo yo, no el pipe: si el pipe pudiera reescribirlo para aprobar, el gate sería un sello de goma.
No agent can verify that an invoice is real or that a shipping charge is right. What is verifiable by execution is that the figure is reproducible from frozen inputs and that every number states its source. The golden checks arithmetic and traceability, not business truth: a human signs that off looking at the real month. The pipe proposes; I confirm. And the golden's ground truth is set by me, not by the pipe: if the pipe could rewrite it to pass, the gate would be a rubber stamp.
Es aritmética pura: precio = (coste × n + costes_fijos) / (1 − margen), redondeado al siguiente precio psicológico terminado en .900, más un pequeño optimizador que busca la progresión de descuentos más creíble. Cero tokens. Un LLM aquí no aportaría nada y sí podría equivocarse distinto cada vez.
It's pure arithmetic: price = (cost × n + fixed_costs) / (1 − margin), rounded up to the next psychological price ending in .900, plus a small optimiser that finds the most believable discount progression. Zero tokens. An LLM would add nothing here and could get it wrong differently every time.
El port JS y el original Python coinciden en los 9 campos, incluidos los bordes (entrega 0 %, CPA explícito).The JS port and the Python original agree on all 9 fields, edge cases included (0 % delivery, explicit CPA).
Redondeo a 2 decimales solo para neutralizar el ruido del float. Nada más se perdona.Rounding to 2 decimals only to neutralise float noise. Nothing else is forgiven.
Clonar la landing de un competidor empezó como lo empieza todo el mundo: mandarle capturas a un modelo de visión y pedirle que la reconstruya. Funcionaba, costaba ~$0,05 por landing y era distinto en cada corrida. Hoy el mismo trabajo lo hacen 26 extractores deterministas y 9 parsers de reseñas sobre el HTML real: mismo input, mismo output, $0. Vision sigue ahí, pero solo detrás de un flag explícito, nunca como fallback silencioso.
Cloning a competitor's landing page started the way everyone starts: send screenshots to a vision model and ask it to rebuild the page. It worked, it cost ~$0.05 per landing and it was different on every run. Today the same job is done by 26 deterministic extractors and 9 review parsers over the real HTML: same input, same output, $0. Vision is still there, but only behind an explicit flag, never as a silent fallback.
Shopify, Funnelish, GemPages, Pagefly y Tiendanube, cada una con su forma de esconder el mismo contenido.Shopify, Funnelish, GemPages, Pagefly and Tiendanube, each hiding the same content its own way.
Un set real de competidores. Se regeneran una a una y se diffea el HTML: el validator en verde no basta.A real competitor set. Regenerated one by one with an HTML diff: a green validator isn't enough.
Partidos por zona (copy · CSS · footer) y con las URLs normalizadas, para que el diff detecte drift y no ruido.Split by zone (copy · CSS · footer) with normalised URLs, so the diff catches drift, not noise.
El precio real de la determinación: cada caso raro de cada builder, escrito y cubierto por el set.The real price of determinism: every builder's weird case, written down and covered by the set.
El validator existía desde hacía semanas, con su lista de placeholders prohibidos y sus checks de estructura. Y aun así, once días de landings con fallos pasaron el gate, porque el gate solo imprimía un warning.
Una defensa que solo avisa entrena a su dueño a ignorarla. Hoy la regla del repo es explícita: toda defensa nueva tiene que abortar: exit ≠ 0, raise, o un fallo detectable aguas abajo. Si hace falta no bloquear en un caso concreto, se hace con un flag visible, no con silencio por defecto.
El mismo principio arregló otro fallo más sutil: un sys.exit() dentro de una librería lanza SystemExit, que no hereda de Exception, así que atravesaba todos los except Exception del dashboard y se llevaba por delante la interfaz entera.
The validator had existed for weeks, with its forbidden-placeholder list and its structure checks. And still, eleven days of broken landings sailed through the gate, because the gate only printed a warning.
A defense that merely warns trains its owner to ignore it. The repo rule is now explicit: every new defense must abort: exit ≠ 0, raise, or a failure detectable downstream. If a specific case genuinely shouldn't block, that's done with a visible flag, not with silence by default.
The same principle fixed a subtler failure: a sys.exit() inside a library raises SystemExit, which doesn't inherit from Exception, so it sailed straight through every except Exception in the dashboard and took the whole UI down.
El activo que no se puede fingir no es el código: es el juicio. Cada una de estas ideas era razonable y tentadora; todas murieron con un dato concreto delante. Varias iban en contra de lo que yo quería que fuera verdad, y por eso están aquí.
The asset you can't fake isn't the code: it's judgment. Every one of these ideas was reasonable and tempting; all of them died with a concrete piece of data in front of them. Several went against what I wanted to be true, which is exactly why they're here.
| IdeaIdea | Por qué tentabaWhy it tempted | Qué encontréWhat I found | VeredictoVerdict |
|---|---|---|---|
| CSV de Meta como fuente de costeMeta CSV as the cost sourceun solo archivo con todo el gasto del mesone file with the whole month's spend | un download y listo, sin OCRone download and done, no OCR | sin desglose por campaña ni estado Pagado/Error; los PDFs sí lo traenno per-campaign breakdown, no Paid/Error state; the PDFs have both | Descartado como costeRuled out as cost |
| Contar el flete de todos los pedidosCount shipping on every orderal fin y al cabo es flete que se pagaafter all, it's shipping that gets paid | parecía más conservadorit looked more conservative | la suma por producto dejaba de cuadrar con el total del mesthe per-product sum stopped matching the month total | Solo 4 estatus finalesFinal 4 statuses only |
| Vision para clonar reseñasVision to clone reviewscada competidor pinta sus reseñas distintoevery competitor renders reviews differently | «un modelo lo resuelve todo»"one model solves everything" | 9 parsers cubren el set entero; sin parser → sección omitida, no invención9 parsers cover the whole set; no parser → section omitted, nothing invented | Fuera del pipe · $0Out of the pipe · $0 |
| Servidor, n8n, FastAPI, PostgresServer, n8n, FastAPI, Postgresla arquitectura «seria»the "serious" architecture | se ve mejor en un diagramait looks better on a diagram | 5–15 tests/semana y un operador → infra sin usuarios y con mantenimiento5–15 tests/week and one operator → infra with no users and real upkeep | Local + SQLiteLocal + SQLite |
| Tasa 1.0 para divisa desconocida1.0 rate for unknown currencyasí el importe nunca sale vacíothat way the amount is never empty | una tabla sin huecos da confianzaa table with no gaps feels trustworthy | subestimaba un coste en USD ~4.200× → podía convertir un perdedor en winnerunderstated a USD cost ~4,200× → could turn a loser into a winner | None + avisowarning |
| Borrar la caché de LLM al cambiar un promptWipe the LLM cache on prompt change«está sucia, mejor empezar limpio»"it's dirty, better start clean" | higiene aparenteapparent hygiene | 268 entradas borradas = 268 llamadas ya pagadas que hubo que repagar268 entries deleted = 268 already-paid calls that had to be paid again | Nunca sin permisoNever without asking |
| Forzar COP en la publicidadForce COP on ad spendhoy todas las cuentas facturan en pesostoday every account bills in pesos | funciona… hasta que deje de hacerloit works… until it doesn't | la primera cuenta que facture en USD escondería un ×4.200 sin avisarthe first USD-billing account would silently hide a ×4,200 | Riesgo documentadoDocumented risk |
La última fila no es un fallo mío que se me olvidó: es un footgun conocido, escrito y fechado en el backlog, con la condición exacta que lo disparará. Prefiero un riesgo documentado y visible a un riesgo resuelto a medias en silencio.
That last row isn't a mistake I forgot about: it's a known footgun, written down and dated in the backlog, with the exact condition that will trigger it. I'd rather have a documented, visible risk than one half-fixed in silence.
Todo el repo está montado sobre una idea aritmética muy simple: si cada paso de una cadena es un 90 % fiable, cinco pasos seguidos te dejan en 59 %. Por eso la IA no ejecuta: orquesta. Las instrucciones viven en workflows en markdown, el juicio lo pone el agente, y el trabajo real lo hacen scripts de Python testeables y repetibles.
The whole repo rests on one very simple piece of arithmetic: if each step in a chain is 90 % reliable, five steps in a row leave you at 59 %. That's why the AI doesn't execute: it orchestrates. Instructions live in markdown workflows, judgment comes from the agent, and the real work is done by testable, repeatable Python scripts.
SOPs en markdown: objetivo, entradas necesarias, qué herramienta usar y cómo tratar los casos raros. Escritos como se le explica a una persona del equipo, y actualizados cuando el proceso aprende algo.Markdown SOPs: objective, required inputs, which tool to use and how to handle edge cases. Written the way you'd brief a teammate, and updated whenever the process learns something.
Lee el workflow, encadena las herramientas en el orden correcto, se recupera de los errores y pregunta cuando falta información. No calcula precios ni conmuta números: conecta intención con ejecución.Reads the workflow, chains the tools in the right order, recovers from errors and asks when information is missing. It doesn't compute prices or crunch numbers: it connects intent to execution.
~34.500 líneas de Python en ecxm/: scraping, pandas, openpyxl, OCR, generación. Consistentes, rápidas y testeables. Toda llamada a un modelo pasa por core/llm.py: un único punto que cachea por hash y contabiliza el coste.~34,500 lines of Python in ecxm/: scraping, pandas, openpyxl, OCR, generation. Consistent, fast and testable. Every model call goes through core/llm.py: a single point that caches by hash and accounts for cost.
Pellizca para acercar y leer el detalle ↔Pinch to zoom in and read the detail ↔
Un cambio de dos líneas en el cierre de finanzas es más peligroso que refactorizar quinientas de estilos. Por eso el proceso no se calibra por líneas cambiadas sino por dónde se toca, y ese conjunto de superficies es inmutable: no lo puede ampliar ni relajar ningún proceso automático.
A two-line change in the finance close is more dangerous than refactoring five hundred lines of styling. So the process is calibrated not by lines changed but by where you touch, and that set of surfaces is immutable: no automated process can widen or relax it.
Ahí el número se convierte en un pago. También cuenta borrar la caché de LLM ya pagada y romper la convención de nombres de campaña.That's where the number becomes a payment. Also: deleting already-paid LLM cache, and breaking the campaign naming convention.
Duele si se rompe, pero se recupera. Aquí el gate ya existe: validator, golden y set de regresión.It hurts when it breaks, but it recovers. The gate already exists here: validator, golden and regression set.
Recomputable y visible al instante. Aquí el proceso pesado sería teatro: leer, cambiar, verificar y decirlo.Recomputable and instantly visible. Heavy process here would be theatre: read, change, verify, say so.
Ningún «el subagente reportó OK» cierra nada aquí: el orquestador corre él mismo el gate y pega la salida real en el prompt del siguiente paso.
No "the subagent reported OK" closes anything here: the orchestrator runs the gate itself and pastes the real output into the next step's prompt.
$ python tests/golden_precios/eval_precios.py check [check] precios — VEREDICTO: PASA (7 casos, igualdad exacta) $ python tests/golden_control_diario/eval_control_diario.py check [check] control_diario — VEREDICTO: PASA (6 casos, igualdad exacta) # Los goldens con cifras reales del negocio NO se versionan: el arnés se # comparte, cada máquina captura su propio baseline. Sin baseline el gate # ni aprueba ni bloquea — avisa de que falta. Un gate que aprueba en vacío # es peor que no tener gate.
$ python tests/golden_precios/eval_precios.py check [check] pricing — VERDICT: PASS (7 cases, exact equality) $ python tests/golden_control_diario/eval_control_diario.py check [check] control_diario — VERDICT: PASS (6 cases, exact equality) # Goldens holding real business figures are NOT versioned: the harness is # shared, each machine captures its own baseline. With no baseline the gate # neither passes nor blocks — it reports the baseline is missing. A gate that # passes on nothing is worse than no gate at all.
Verde local ≠ correcto. Que el dashboard arranque no dice nada sobre si la utilidad neta que calculó el pivot es la real. La única señal con dientes es el gate golden, y lo corro yo antes de cerrar nada.Green locally ≠ correct. The dashboard booting says nothing about whether the net profit the pivot computed is the real one. The only signal with teeth is the golden gate, and I run it myself before closing anything.
Al encontrar un fallo, la pregunta obligatoria es: ¿existe el mismo bug en los caminos análogos? Un invariante como «el flete solo cuenta en los 4 estatus finales» vive en un sitio y lo llaman todas las etapas: ingerir, limpiar, cruzar, pivotar y cerrar.On finding a defect, the mandatory question is: does the same bug exist in the analogous paths? An invariant like "shipping only counts on the 4 final statuses" lives in one place and is called by every stage: ingest, clean, cross-match, pivot and close.
El sistema está en producción y se usa todos los días, pero no está «terminado», y decir lo contrario sería el primer número falso de esta página.
The system is in production and used every day, but it isn't "finished", and saying otherwise would be the first false number on this page.
El resto del método —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 —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.
Si buscas a alguien para applied-AI, automatización de operaciones o forward-deployed engineering, que construye sistemas para que la IA no se engañe a sí misma ni a ti, hablemos.
If you're looking for someone in applied-AI, operations automation or forward-deployed engineering who builds systems so the AI can't fool itself or you, let's talk.
Todo lo de esta página lo construí en solitario, con IA y sin formación técnica previa.Everything on this page I built solo, with AI and with no prior technical background.