[{"data":1,"prerenderedAt":606},["ShallowReactive",2],{"navigation":3,"/blog/case-studies/retail-ecommerce-recovery":51,"/blog/case-studies/retail-ecommerce-recovery-surround":601},[4],{"title":5,"path":6,"stem":7,"children":8,"page":34},"Blog","/blog","blog",[9,13,35,39,43,47],{"title":10,"path":11,"stem":12},"Using the official exchange rate in Venezuela's e-commerce","/blog/bcv-scraper","blog/bcv-scraper",{"title":14,"path":15,"stem":16,"children":17,"page":34},"Case Studies","/blog/case-studies","blog/case-studies",[18,22,26,30],{"title":19,"path":20,"stem":21},"Rebuilding an e-commerce platform on NestJS + Next.js + Medusa in three months","/blog/case-studies/retail-ecommerce-rebuild","blog/case-studies/retail-ecommerce-rebuild",{"title":23,"path":24,"stem":25},"Stabilizing an inherited e-commerce platform on NestJS + Strapi","/blog/case-studies/retail-ecommerce-recovery","blog/case-studies/retail-ecommerce-recovery",{"title":27,"path":28,"stem":29},"A nationwide mobile sales-quotation app, integrated with SAP","/blog/case-studies/retail-sap-quotation-app","blog/case-studies/retail-sap-quotation-app",{"title":31,"path":32,"stem":33},"Shipping a high-scale PWA from an inherited e-commerce project","/blog/case-studies/supermarket-pwa-recovery","blog/case-studies/supermarket-pwa-recovery",false,{"title":36,"path":37,"stem":38},"From Nuxt to Hugo and Back Again","/blog/from-nuxt-to-hugo","blog/from-nuxt-to-hugo",{"title":40,"path":41,"stem":42},"The Intern is downloading movies again","/blog/intern-movie-torrent","blog/intern-movie-torrent",{"title":44,"path":45,"stem":46},"Japanese input in openSUSE Tumbleweed's KDE","/blog/japanese-input","blog/japanese-input",{"title":48,"path":49,"stem":50},"Jhey Pi: an official landing site for an aspiring musician","/blog/jp-landing","blog/jp-landing",{"id":52,"title":23,"author":53,"body":59,"category":574,"client":575,"date":576,"description":577,"extension":578,"featured":579,"image":580,"meta":581,"minRead":582,"navigation":579,"ogImage":580,"outcome_headline":583,"path":24,"repoUrl":582,"role":584,"seo":585,"stack":586,"stem":25,"tags":592,"team_size":597,"type":598,"year":599,"__hash__":600},"blog/blog/case-studies/retail-ecommerce-recovery.md",{"name":54,"username":55,"to":56,"avatar":57},"Iván Álvarez","ivanovertime","https://github.com/ivanovertime",{"src":58,"alt":54},"/avatar.jpg",{"type":60,"value":61,"toc":558},"minimark",[62,90,95,130,134,137,151,157,161,228,234,238,243,246,286,289,293,296,332,339,343,346,382,388,392,395,431,434,438,441,474,485,489,492,543,546,549],[63,64,65],"blockquote",{},[66,67,68,76,77,81,82,89],"p",{},[69,70],"u-icon",{"className":71,"name":75},[72,73,74],"inline","mr-1","align-[-2px]","i-lucide-paperclip"," ",[78,79,80],"strong",{},"Part of a pair."," This is Act 1 of two consecutive e-commerce engagements with the same retail organization. The follow-on — the from-scratch rebuild on NestJS + Next.js + Medusa — is here: ",[83,84,85],"a",{"href":20},[86,87,88],"em",{},"Rebuilding an e-commerce platform in three months",".",[91,92,94],"h2",{"id":93},"tldr","TL;DR",[96,97,98,106,124],"ul",{},[99,100,101,102,105],"li",{},"Joined as tech lead on an inherited ",[78,103,104],{},"NestJS + Strapi"," e-commerce stack with a SAP source of truth.",[99,107,108,109,112,113,112,116,119,120,123],{},"With a team of 4 we ",[78,110,111],{},"stabilized the runtime first",", ",[78,114,115],{},"contained SAP behind a single integration boundary",[78,117,118],{},"containerized the deploy path",", and ",[78,121,122],{},"kept Strapi to editorial content"," rather than letting it drift into a control plane for business logic.",[99,125,126,127,89],{},"The result was a release cadence the team was comfortable shipping on, and an integration boundary clean enough to carry forward into the ",[83,128,129],{"href":20},"rebuild case study",[91,131,133],{"id":132},"context","Context",[66,135,136],{},"This engagement was with a large retail organization in Venezuela. The shape of the work is familiar to anyone who has done platform-recovery work:",[96,138,139,142,145,148],{},[99,140,141],{},"An inherited NestJS + Strapi codebase, with SAP behind it as the source of truth for catalog, pricing, and inventory.",[99,143,144],{},"A release path that the team didn't fully trust, in the way platforms tend to drift when they've been built by hands no longer on the keyboard.",[99,146,147],{},"A SAP integration spread across the codebase, each call site with its own retry logic and failure modes — a common shape, not specific to any one client.",[99,149,150],{},"A roadmap that the team wanted to be able to plan against with confidence again.",[66,152,153,154],{},"The brief was simple to state and harder to do: ",[86,155,156],{},"make this safe to release on a cadence.",[91,158,160],{"id":159},"constraints","Constraints",[162,163,164,177],"table",{},[165,166,167],"thead",{},[168,169,170,174],"tr",{},[171,172,173],"th",{},"Constraint",[171,175,176],{},"Reality",[178,179,180,189,197,204,212,220],"tbody",{},[168,181,182,186],{},[183,184,185],"td",{},"Team size",[183,187,188],{},"4 engineers, with me as tech lead",[168,190,191,194],{},[183,192,193],{},"Calendar",[183,195,196],{},"One quarter to show measurable stability",[168,198,199,202],{},[183,200,201],{},"Inherited stack",[183,203,104],{},[168,205,206,209],{},[183,207,208],{},"Source of truth",[183,210,211],{},"SAP — for catalog, pricing, inventory",[168,213,214,217],{},[183,215,216],{},"Off-limits",[183,218,219],{},"A full rewrite. The storefront had to keep serving customers throughout.",[168,221,222,225],{},[183,223,224],{},"Safety net",[183,226,227],{},"Whatever automated coverage we wanted to lean on, we'd be building as we went.",[66,229,230,231],{},"The non-negotiable: ",[78,232,233],{},"keep the storefront online for customers, every day, throughout the recovery.",[91,235,237],{"id":236},"decisions","Decisions",[239,240,242],"h3",{"id":241},"decision-1-stabilize-the-runtime-before-touching-features","Decision 1 — Stabilize the runtime before touching features",[66,244,245],{},"The first two weeks were diagnosis only. We wrote down what we found, what we'd touch, and what we'd explicitly leave alone.",[162,247,248,261],{},[165,249,250],{},[168,251,252,255,258],{},[171,253,254],{},"Option",[171,256,257],{},"Trade-off",[171,259,260],{},"Chose",[178,262,263,273],{},[168,264,265,268,271],{},[183,266,267],{},"Fix the highest-priority bug list first",[183,269,270],{},"Visible to stakeholders, but every fix on an unfamiliar runtime is its own risk",[183,272],{},[168,274,275,278,281],{},[183,276,277],{},"Freeze features for two weeks, audit and instrument the runtime",[183,279,280],{},"Feels slow to the business; pays back the moment the next change lands",[183,282,283],{},[284,285],"decision-check",{},[66,287,288],{},"We bought predictability before we bought velocity. Every subsequent change landed on a runtime we actually understood.",[239,290,292],{"id":291},"decision-2-contain-sap-integration-behind-a-single-explicit-boundary","Decision 2 — Contain SAP integration behind a single explicit boundary",[66,294,295],{},"The SAP integration was the highest-leverage place to invest. Catalog and pricing reads were spread across the codebase, each call site with its own retry logic, timeouts, and failure modes — a pattern any engineer who has worked alongside an ERP will recognise.",[162,297,298,308],{},[165,299,300],{},[168,301,302,304,306],{},[171,303,254],{},[171,305,257],{},[171,307,260],{},[178,309,310,320],{},[168,311,312,315,318],{},[183,313,314],{},"Refactor the integration in place, file by file",[183,316,317],{},"Low blast radius per PR, but no clear \"done\" line",[183,319],{},[168,321,322,325,328],{},[183,323,324],{},"Introduce a single integration module with a typed interface and route every call through it",[183,326,327],{},"Higher up-front cost; produces one place to harden, log, and (later) cache",[183,329,330],{},[284,331],{},[66,333,334,335,338],{},"The module became the seam that made everything else easier — observability, retries, caching. It also turned out to be the single most reusable artifact of the whole engagement: it survived the rebuild that followed, and the ",[83,336,337],{"href":28},"SAP quotation app"," reused the same pattern.",[239,340,342],{"id":341},"decision-3-containerize-the-deploy-path-dont-rebuild-the-platform","Decision 3 — Containerize the deploy path, don't rebuild the platform",[66,344,345],{},"Rather than chase a new platform target during recovery, we containerized the existing services and standardized the deploy path.",[162,347,348,358],{},[165,349,350],{},[168,351,352,354,356],{},[171,353,254],{},[171,355,257],{},[171,357,260],{},[178,359,360,370],{},[168,361,362,365,368],{},[183,363,364],{},"Migrate to a managed PaaS in the middle of a recovery",[183,366,367],{},"Modern, but introduces a second migration on top of an unstable baseline",[183,369],{},[168,371,372,375,378],{},[183,373,374],{},"Containerize in place + a thin CI/CD pipeline",[183,376,377],{},"Familiar tooling, fast payoff, leaves the door open for Kubernetes or Cloud Run later",[183,379,380],{},[284,381],{},[66,383,384,385],{},"The recurring pattern in this kind of work: ",[78,386,387],{},"fewer moving parts at first, more options later.",[239,389,391],{"id":390},"decision-4-make-strapi-a-content-tool-not-a-control-plane","Decision 4 — Make Strapi a content tool, not a control plane",[66,393,394],{},"Strapi had drifted into being used for things it isn't good at — pricing rules, inventory flags, business logic that lived behind editorial fields. Every content edit was, accidentally, a code change.",[162,396,397,407],{},[165,398,399],{},[168,400,401,403,405],{},[171,402,254],{},[171,404,257],{},[171,406,260],{},[178,408,409,419],{},[168,410,411,414,417],{},[183,412,413],{},"Leave the responsibilities where they were and document them",[183,415,416],{},"Cheaper now; pays the same incident tax forever",[183,418],{},[168,420,421,424,427],{},[183,422,423],{},"Pull business logic back into NestJS; restrict Strapi to editorial content",[183,425,426],{},"More work now; clears a category of surprise entirely",[183,428,429],{},[284,430],{},[66,432,433],{},"This bought breathing room and — as it turned out — clarified what a follow-on rebuild would and would not need to replace.",[91,435,437],{"id":436},"outcome","Outcome",[66,439,440],{},"By the end of the engagement:",[96,442,443,449,456,462,465],{},[99,444,445,446,89],{},"The storefront shipped on a ",[78,447,448],{},"predictable, repeatable cadence",[99,450,451,452,455],{},"Every SAP read and write flowed through the ",[78,453,454],{},"single integration module",", which became the one place to harden, log, and cache.",[99,457,458,461],{},[78,459,460],{},"Deployments became reproducible"," — same artifact, same path, every time.",[99,463,464],{},"The product team had a platform they could plan a roadmap against.",[99,466,467,468,471,472,89],{},"The integration-module pattern became the ",[78,469,470],{},"most reusable asset"," of the engagement, carried forward into the rebuild and into the ",[83,473,337],{"href":28},[66,475,476,477,480,481,89],{},"The deeper outcome was the one that made Act 2 possible at all: with the runtime stable, the business asked the more interesting question — ",[86,478,479],{},"\"if you had three months, how would you build this from scratch?\""," That answer is its own case study: ",[83,482,483],{"href":20},[86,484,88],{},[91,486,488],{"id":487},"what-id-do-on-gcp-today","What I'd do on GCP today",[66,490,491],{},"Same brief in 2026, same inherited stack. The architectural instincts wouldn't change — the platform underneath would.",[96,493,494,504,514,524,534],{},[99,495,496,499,500,503],{},[78,497,498],{},"Runtime:"," containerize for ",[78,501,502],{},"Cloud Run"," rather than self-managed Docker hosts. Same artifact, no host-management work.",[99,505,506,509,510,513],{},[78,507,508],{},"SAP integration:"," keep the single-module pattern, and put ",[78,511,512],{},"Pub/Sub"," between SAP events and the NestJS consumer so the storefront degrades gracefully when SAP is slow.",[99,515,516,519,520,523],{},[78,517,518],{},"Catalog & pricing reads:"," project SAP data into ",[78,521,522],{},"Firestore"," (or a small Postgres on Cloud SQL) for hot reads, with SAP staying the system of record. Stops the storefront from being coupled to SAP's response time.",[99,525,526,529,530,533],{},[78,527,528],{},"Observability:"," wire ",[78,531,532],{},"Cloud Logging + Cloud Trace"," from day one. The first thing a recovery needs is a working flashlight.",[99,535,536,76,539,542],{},[78,537,538],{},"Delivery:",[78,540,541],{},"GitHub Actions → Cloud Run",", with a preview environment per PR. The same predictability we earned manually, but for free.",[66,544,545],{},"The pattern is the same. The leverage is higher.",[547,548],"hr",{},[66,550,551],{},[86,552,553,554,89],{},"If you're staring at an inherited platform that feels too risky to release and too expensive to rewrite, the answer is rarely either-or. Recover first, then earn the right to rebuild. Happy to talk through it — ",[83,555,557],{"href":556},"/contact","get in touch",{"title":559,"searchDepth":560,"depth":560,"links":561},"",2,[562,563,564,565,572,573],{"id":93,"depth":560,"text":94},{"id":132,"depth":560,"text":133},{"id":159,"depth":560,"text":160},{"id":236,"depth":560,"text":237,"children":566},[567,569,570,571],{"id":241,"depth":568,"text":242},3,{"id":291,"depth":568,"text":292},{"id":341,"depth":568,"text":342},{"id":390,"depth":568,"text":391},{"id":436,"depth":560,"text":437},{"id":487,"depth":560,"text":488},"case-study"," Retail Client (LatAm)","2026-05-15T00:00:00.000Z","A recovery-shaped engagement on an inherited NestJS + Strapi storefront: stabilize the runtime, contain the SAP integration behind a single boundary, containerize the deploy path, and keep Strapi to editorial content. This is the recovery half of a paired engagement; the rebuild that followed is its own story.\n","md",true,"/blog/case-studies/retail-ecommerce-core/featuredImage.jpg",{},null,"Built a predictable release path on an inherited stack and a clean SAP integration boundary that set up the work that came next","Tech Lead",{"title":23,"description":577},[587,588,589,590,591],"NestJS","Strapi","Docker","SAP","CI/CD",[593,594,595,596],"e-commerce","platform-recovery","integrations","nestjs",4,"recovery",2025,"aKsVjbv4dqTgE2FKuvlco3_aZt6HNtsDBQ8Dq9Q0jtA",[602,604],{"title":19,"path":20,"stem":21,"description":603,"children":-1},"After the recovery, the next engagement was a from-scratch e-commerce build on NestJS + Next.js + Medusa, delivered with a team of 4 inside a three-month window. SAP stayed as the source of truth, the integration boundary from the recovery was reused as a contract, and the cutover was planned from day one.\n",{"title":27,"path":28,"stem":29,"description":605,"children":-1},"Architecting and shipping a mobile-first sales-quotation application on Laravel + Filament, integrated with SAP as the source of truth for catalog and pricing, and rolled out to sales teams across the country.\n",1779739958195]