MEMORIA FUNCIONAL — ShopifySync
Documento generado a partir del código real del proyecto (
code/frontend/React SPA +code/backend/FastAPI). Fecha: 2026-08-05 · Live: https://shopify-sync.theboomer.dev
1. Introducción
ShopifySync es un SaaS que sincroniza productos entre tiendas Shopify y Google Sheets en ambas direcciones. El usuario conecta sus tiendas Shopify (dominio + access token) y sus hojas de cálculo de Google (spreadsheet ID + tokens), configura el mapeo de columnas (qué columna de la hoja corresponde a qué campo de Shopify) y ejecuta sincronizaciones manuales, con planes de pago vía Stripe (Free / Pro / Pro API).
Arquitectura real
| Capa | Tecnología | Ruta |
|---|---|---|
| Frontend | React + Vite + React Router + TanStack Query + Zustand (persist) + Tailwind + sonner (toasts) | code/frontend/src/ |
| Backend | FastAPI (Python) — routers: auth, billing, sync, sync_history, webhooks, sync_config | code/backend/app/ |
| Servicios | Shopify API (paginación por cursor, inventory levels), Google Sheets API, motor de sync, Stripe | code/backend/app/services/ |
| Persistencia | Repositorios de usuario/tienda/hoja/config (Motor/MongoDB async) | code/backend/app/models/ |
Stack frontend real: App.tsx monta QueryClientProvider (react-query) + BrowserRouter + Toaster (sonner). La autenticación se guarda en Zustand con persist bajo la clave shopify-sync-auth. El API client (lib/api.ts) apunta a VITE_API_URL (default http://localhost:9100) y añade Authorization: Bearer <token>.
Nota: el backend eliminó los endpoints OAuth (# OAuth endpoints removed - using direct token input instead): el usuario pega los tokens de Google/Shopify directamente en los formularios.
2. Tipos de usuario
| Tipo | Descripción | Alcance real en el código |
|---|---|---|
| Visitante | Sin sesión | Redirigido a /login (guard del layout). |
| Usuario autenticado | Login con email (o registro con email + nombre) | Accede al dashboard completo: Stores, Sheets, Mapping, Settings. Plan por defecto free. |
| Plan Free | $0 | 1 tienda, 1 hoja, sync manual (según tarjetas de planes en Settings). |
| Plan Pro | $7/mes | 5 tiendas, 5 hojas, auto sync, soporte prioritario (precios/features de UI). |
| Plan Pro API | $12/mes | Tiendas y hojas ilimitadas, acceso API, soporte prioritario. |
3. Funcionalidades
F3.1 — Autenticación (Login / Registro) — pages/Login.tsx
- Descripción: pantalla única con modo login/registro alternable.
- Flujo:
1. Por defecto modo Sign In (solo email). Toggle
Need an account? Sign upmuestra además el campo Name. 2.login(email)→POST /auth/loginoregister(email, name)→POST /auth/register. 3. Guarda{user, access_token}en el store Zustand (setAuth) →navigate("/"). 4. Si falla: muestra el mensaje del error (ApiError.detailo"Something went wrong") en un panel rojo. - UI: email (type=email, required), name (required solo en registro), botón negro
Sign In/Create Accountcon spinner mientrasloading. - Validaciones: HTML5
requireden los campos; deshabilitado del botón mientras carga; error visible en banner rojo.
F3.2 — Layout de dashboard con guard de auth — components/DashboardLayout.tsx
- Descripción: shell de la app autenticada: sidebar oscura + contenido.
- Flujo: si
!isAuthenticated→navigate("/login")y no renderiza nada. Si autenticado → sidebar con: - Logo/título
ShopifySync. - Nav: Dashboard (
/), Stores (/stores), Sheets (/sheets), Mapping (/mapping), Settings (/settings); el ítem activo se marca (bg-slate-800 text-white). - Bloque de usuario: nombre + email, botón Logout (
clearAuth()→/login). - Contenido:
<Outlet />dentro demax-w-6xl mx-auto, fondobg-slate-50.
F3.3 — Dashboard / Home — pages/Home.tsx
- Descripción: vista principal con estadísticas y acciones rápidas.
- Carga:
Promise.all([getStores(), getSheets()])al montar; spinner centrado mientras carga. - Tarjetas de stats (grid 3 cols):
- Connected Stores —
stores.length(icono azul). - Connected Sheets —
sheets.length(icono verde). - Products Synced —
lastSync?.products_synced || 0(icono púrpura). - Quick Actions:
- Si no hay tiendas u hojas: mensaje
Connect at least one store and one sheet to start syncing.+ enlacesAdd Store →(/stores) yAdd Sheet →(/sheets). - Si hay: botón Sync Now (spinner mientras corre) + texto
{stores[0].shopify_domain} → {sheets[0].spreadsheet_name}. handleSync→runSync(stores[0].id, sheets[0].id, "shopify_to_sheet"); resultado guardado enlastSync.- Tras sincronizar: banner verde
Synced {N} products successfully. - Connected Services: lista de tiendas (dominio) y hojas (nombre) conectadas; si no hay ninguna:
No services connected yet. - Reglas reales del código: la sincronización rápida usa siempre la primera tienda y la primera hoja y dirección fija
shopify_to_sheet.
F3.4 — Stores (tiendas Shopify) — pages/Stores.tsx
- Descripción: conexión y gestión de tiendas Shopify.
- Formulario "Add New Store":
- Shopify Domain (text, placeholder
mystore.myshopify.com, required). - Access Token (password, placeholder
shpat_..., required). - Botón
+ Add Store→createStore(domain, token)→POST /sync/store; limpia campos y recarga la lista. - Lista "Connected Stores (N)":
- Cada tienda: dominio +
shopify_location_id || "No location set". - Botón papelera →
confirm("Delete this store?")→deleteStore(id)→DELETE /sync/stores/{id}; spinner en el ítem borrado. - Estados: loading (spinner central), empty (
No stores connected), error (console.error, sin UI de error visible).
F3.5 — Sheets (hojas de Google) — pages/Sheets.tsx
- Descripción: conexión y gestión de Google Sheets.
- Formulario "Add New Sheet":
- Spreadsheet ID (text, placeholder
1abc..., required). - Spreadsheet Name (text, placeholder
My Inventory, required). - Sheet Name (text, default
Sheet1). - Google Access Token (password, placeholder
ya29..., required). - Google Refresh Token (password, placeholder
1//..., required). - Botón
+ Add Sheet→createSheet(...)→POST /sync/sheet; limpia campos y recarga. - Lista "Connected Sheets (N):" cada hoja muestra
spreadsheet_name+sheet_name • spreadsheet_id.slice(0,15)...; borrado conconfirm("Delete this sheet?")→DELETE /sync/sheets/{id}.
F3.6 — Mapping de columnas — pages/Mapping.tsx
- Descripción: configuración del mapeo columna-de-hoja ↔ campo-de-Shopify y dirección de la sincronización.
- Lista de configuraciones (
SyncConfigurations): botón+ Add Configuration; cada config muestrastoreName,sheetName → Shopify|Sheetsegún dirección; acciones Edit y papelera (delete con confirm). - Modal Create/Edit (overlay
bg-black/50, card max-w-2xl, scrollable): - Shopify Store (input texto, placeholder
mystore.myshopify.com). - Google Sheet (input texto, placeholder
Inventory). - Sync Direction (select):
Sheet → Shopify(sheet_to_shopify) /Shopify → Sheet(shopify_to_sheet). - Column Mapping: filas
[columna de la hoja] → [campo Shopify ▾]con botón+ Add Mappingy papelera por fila.- Campo hoja: input texto con
datalistde sugerencias: SKU, Product, Stock, Price, Barcode, Weight (kg), Category, Active. - Campo Shopify: select con SKU, Product Title, Price, Inventory Quantity, Barcode, Weight, Grams, Requires Shipping, Taxable (
sku, title, price, inventory_quantity, barcode, weight, grams, requires_shipping, taxable).
- Campo hoja: input texto con
- Acciones: Cancel / Save (spinner mientras guarda).
- Save: si la config tiene
id→PATCH /sync/config/{id}(updateSyncConfig con direction+mapping); si no →POST /sync/config(createSyncConfig constoreId/sheetId). Tras guardar recarga y cierra el modal. - Datos locales vs API: la UI trabaja con nombres (
storeName,sheetName) pero la API esperastore_id/sheet_id; al cargar se mapean los IDs a nombres (mostrando el ID como nombre) y al crear una config nueva se envían""como store/sheet id (observación de comportamiento real).
F3.7 — Settings (cuenta y billing) — pages/Settings.tsx
- Descripción: plan actual, planes disponibles y datos de cuenta.
- Current Plan: nombre del plan (capitalizado,
user?.plan || "free") +Billing activesi haystripe_customer_idoFree tier; si el plan no es free → botónManage Billing →→createPortal()(POST /billing/portal) → redirige aresult.url. - Available Plans (grid 3 cols):
- Free — $0 — 1 store, 1 sheet, Manual sync.
- Pro — $7/mo — 5 stores, 5 sheets, Auto sync, Priority support.
- Pro API — $12/mo — Unlimited stores, Unlimited sheets, API access, Priority support.
- Botón Upgrade →
createCheckout(planId, "monthly")(POST /billing/checkout) →window.location.href = result.url. El plan actual muestra botónCurrentdeshabilitado. - Account Information: Name y Email (read-only, del store de auth).
F3.8 — Sincronización (motor backend) — app/services/sync_engine.py + app/api/sync.py
POST /sync/run{store_id, sheet_id, direction}→{status, store_id, sheet_id, direction, products_synced, errors}.- Dirección
shopify_to_sheet: paginación de productos Shopify (50 por página con cursor) → consulta inventory levels de todos losinventory_item_id→ escribe en la hoja desde fila 2, columna A, columnas SKU y Stock (sheet_name="Sheet1"). - Dirección
sheet_to_shopify: lee productos de la hoja (get_products_data), descarga productos actuales de Shopify (250), construye mapa por SKU y actualiza stock en Shopify. - Cada dirección devuelve
products_syncedy lista deerrors(si algo falla, se captura y se reporta sin romper la respuesta).
F3.9 — API de configuración de sync — app/api/sync_config.py
GET /sync/config→ lista de configs{id, store_id, sheet_id, direction, mapping: [{sheet_column, shopify_field}]}.POST /sync/config→ crea (store_id, sheet_id, direction, mapping).PATCH /sync/config/{id}→ actualizadirectiony/omapping.DELETE /sync/config/{id}→ elimina.
F3.10 — Billing Stripe — app/api/billing.py + app/services/stripe.py
POST /billing/checkout{plan, interval}→{url, session_id}(redirección del frontend al checkout de Stripe).POST /billing/portal→{url, session_id}(customer portal para gestionar la suscripción).- Planes en backend alineados con la UI: free / pro / pro_api (mensual por defecto).
F3.11 — Webhooks y rate limiting (backend)
- Router
webhooks(app/api/webhooks.py) registrado en la app. app/core/rate_limit.pyexiste en el núcleo (limitación de peticiones, usada por la API).
4. Pantallas (wireframes textuales)
P1 — Login (/login)
┌────────────────────────────────────────────────────┐
│ (centrado vertical/horizontal, bg-slate-50)│
│ ┌──────────────────────────────────────────────┐ │
│ │ [🛍 icono] ShopifySync │ │
│ │ │ │
│ │ [banner rojo si error] │ │
│ │ │ │
│ │ Email │ │
│ │ [________________________] │ │
│ │ Name (solo en modo registro) │ │
│ │ [________________________] │ │
│ │ │ │
│ │ [ Sign In / Create Account ] (negro)│ │
│ │ │ │
│ │ Need an account? Sign up (toggle azul) │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
P2 — Dashboard (/)
┌──────────────┬───────────────────────────────────────────┐
│ SIDEBAR │ Dashboard │
│ (slate-900) │ Manage your Shopify and Google Sheets sync│
│ │ │
│ ShopifySync │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ │ [🛍] 2 │ │ [📋] 1 │ │ [📦] 0 │ │
│ ● Dashboard │ │ Stores │ │ Sheets │ │ Synced │ │
│ Stores │ └──────────┘ └──────────┘ └──────────┘ │
│ Sheets │ │
│ Mapping │ Quick Actions │
│ Settings │ [↻ Sync Now] mistore.myshopify.com → Inv│
│ │ (o: Connect at least one store and one │
│ ─────────────│ sheet… [Add Store →] [Add Sheet →]) │
│ 👤 Nombre │ [banner verde: Synced N products ...] │
│ email │ │
│ [⏻ Logout] │ Connected Services │
│ │ [🛍] mistore.myshopify.com │
│ │ [📋] My Inventory │
└──────────────┴───────────────────────────────────────────┘
P3 — Stores (/stores)
┌──────────────┬───────────────────────────────────────────┐
│ SIDEBAR │ Stores │
│ (activa: │ Connect your Shopify stores │
│ Stores) │ │
│ │ Add New Store │
│ │ Shopify Domain [mystore.myshopify.com] │
│ │ Access Token [•••••••• (shpat_...)] │
│ │ [+ Add Store] │
│ │ │
│ │ Connected Stores (2) │
│ │ [🛍] mystore.myshopify.com [🗑] │
│ │ <location_id> / No location set │
│ │ (vacío → "No stores connected") │
└──────────────┴───────────────────────────────────────────┘
P4 — Sheets (/sheets)
┌──────────────┬───────────────────────────────────────────┐
│ SIDEBAR │ Sheets │
│ (activa: │ Connect your Google Sheets │
│ Sheets) │ │
│ │ Add New Sheet │
│ │ Spreadsheet ID [1abc...] Name [My Inv.] │
│ │ Sheet Name [Sheet1] │
│ │ Google Access Token [•••• (ya29...)] │
│ │ Google Refresh Token [•••• (1//...)] │
│ │ [+ Add Sheet] │
│ │ │
│ │ Connected Sheets (1) │
│ │ [📋] My Inventory [🗑] │
│ │ Sheet1 • 1abc12345678901... │
└──────────────┴───────────────────────────────────────────┘
P5 — Mapping (/mapping)
┌──────────────┬───────────────────────────────────────────┐
│ SIDEBAR │ Column Mapping │
│ (activa: │ Configure which columns in your Google │
│ Mapping) │ Sheet map to Shopify fields │
│ │ │
│ │ Sync Configurations [+ Add Config] │
│ │ [⇄] <store_id> [Edit] [🗑] │
│ │ <sheet_id> → Shopify │
│ │ (vacío → "No sync configurations yet.") │
│ │ │
│ │ ┌ MODAL (overlay) ────────────────────┐ │
│ │ │ Create/Edit Sync Configuration │ │
│ │ │ Shopify Store [________] Sheet [__]│ │
│ │ │ Sync Direction [Sheet → Shopify ▾] │ │
│ │ │ Column Mapping [+ Add Mapping]│ │
│ │ │ [SKU ▾] → [SKU ▾] [🗑] │ │
│ │ │ [Stock ▾] → [Inventory Qty ▾] [🗑] │ │
│ │ │ [Cancel][Save] │ │
│ │ └────────────────────────────────────┘ │
└──────────────┴───────────────────────────────────────────┘
P6 — Settings (/settings)
┌──────────────┬───────────────────────────────────────────┐
│ SIDEBAR │ Settings │
│ (activa: │ Manage your account and billing │
│ Settings) │ │
│ │ Current Plan │
│ │ ┌─────────────────────────────────────┐ │
│ │ │ Pro Billing active │ │
│ │ │ [Manage Billing →]│ │
│ │ └─────────────────────────────────────┘ │
│ │ │
│ │ Available Plans │
│ │ ┌ Free ──────┐ ┌ Pro ───────┐ ┌ Pro API ┐│
│ │ │ $0 │ │ $7/mo │ │ $12/mo ││
│ │ │ ✓1 store │ │ ✓5 stores │ │ ✓Unlim. ││
│ │ │ ✓1 sheet │ │ ✓5 sheets │ │ ✓API ││
│ │ │ ✓Manual │ │ ✓Auto sync │ │ ✓Priori ││
│ │ │ [Current] │ │ [Current] │ │[Upgrade]││
│ │ └────────────┘ └────────────┘ └─────────┘│
│ │ │
│ │ Account Information │
│ │ Name Juan │
│ │ Email juan@example.com │
└──────────────┴───────────────────────────────────────────┘
5. Flujos de trabajo
Flujo A — Primer uso (registro → conexión → sync)
- El visitante accede a
/→ redirigido a/login(guard del layout). - Hace Sign up (email + nombre) o Sign In (email) → token guardado en Zustand persist → vuelve a
/. - En
/storesañade su tienda Shopify (dominio +shpat_...) →POST /sync/store. - En
/sheetsañade su hoja (spreadsheet ID/nombre, sheet name, tokens Google) →POST /sync/sheet. - En
/mappingcrea una configuración (store, sheet, dirección, mapeo de columnas) →POST /sync/config. - En el dashboard pulsa Sync Now →
POST /sync/run(usa la 1ª tienda y 1ª hoja,shopify_to_sheet) → banner verde con nº de productos sincronizados. - Si el plan lo permite (Pro+), puede gestionar billing desde Settings.
Flujo B — Conexión de una tienda
- Abrir
/stores→ rellenarShopify Domain+Access Token(campos required). - Pulsar
Add Store→ POST; en error solo se loguea en consola (sin mensaje UI). - La tienda aparece en
Connected Stores (N)con su location (oNo location set). - Eliminación: papelera →
confirm("Delete this store?")→ DELETE → recarga.
Flujo C — Configurar un mapeo de columnas
- Abrir
/mapping→+ Add Configuration. - En el modal: escribir store y sheet, elegir dirección (
Sheet → ShopifyoShopify → Sheet). - Añadir filas de mapeo: columna de la hoja (con sugerencias SKU/Product/Stock/…) → campo Shopify (select SKU/Title/Price/Inventory…).
Save→ POST (nuevo) o PATCH (existente) → la config aparece en la lista con su dirección.- Editar reabre el modal con los datos; borrar pide confirmación.
Flujo D — Sincronización Shopify → Sheet
- Backend pagina los productos de Shopify (50/cursor) hasta agotar.
- Recoge todos los
inventory_item_idy consulta inventory levels. - Construye filas
{SKU, Stock}y las escribe en la hoja desde fila 2, columna A. - Responde
products_syncedyerrors(si hubo). El dashboard muestra el resultado.
Flujo E — Sincronización Sheet → Shopify
- Backend lee los productos de la hoja.
- Obtiene productos actuales de Shopify (250) y construye mapa por SKU.
- Actualiza stock en Shopify según los valores de la hoja.
- Devuelve
products_synced/errors.
Flujo F — Upgrade de plan
/settings→ ver plan actual + tarjetas de planes.Upgradeen Pro o Pro API →POST /billing/checkout→ redirección a Stripe Checkout.- Tras pagar,
stripe_customer_idqueda en el usuario → Settings muestraBilling activeyManage Billing →(portal).
Flujo G — Cierre de sesión
- Botón
Logouten la sidebar →clearAuth()(borra token/usuario del store persistido) →/login.
6. Reglas de negocio
- Acceso restringido: toda ruta del dashboard requiere
isAuthenticated; si no, redirección a/login. - Auth por email (magic/simple): login solo con email; registro con email + nombre; token Bearer almacenado en localStorage (Zustand persist).
- Límites por plan (según UI de Settings): Free = 1 tienda, 1 hoja, sync manual; Pro = 5 tiendas, 5 hojas, auto sync, soporte prioritario; Pro API = ilimitado, acceso API. (Los límites se muestran en la UI; el enforcement efectivo depende del backend/Stripe.)
- Conexiones por token directo: no hay OAuth en UI; el usuario pega
shopify_access_tokeny los tokens de Google (access+refresh) en los formularios (los campos se muestran como password). - Sync rápido del dashboard: usa
stores[0]ysheets[0]con dirección fijashopify_to_sheet; el mapeo avanzado se gestiona en/mapping. - Direcciones válidas de sync:
sheet_to_shopify(Sheet → Shopify) yshopify_to_sheet(Shopify → Sheet). - Formato de la hoja: para sync se espera hoja llamada
Sheet1con columnas SKU y Stock (SKU/Stock a partir de fila 2 enshopify_to_sheet; ensheet_to_shopifyse leen los productos de la hoja). - Campos mapeables de Shopify:
sku,title,price,inventory_quantity,barcode,weight,grams,requires_shipping,taxable. - Eliminaciones con confirmación: tiendas, hojas y configuraciones piden
confirm()antes de borrar. - Billing mensual: checkout y portal usan intervalo
monthly; plan por defectofree;stripe_customer_idindica billing activo. - Tolerancia a errores de sync: los errores de Shopify/Sheets se capturan y se devuelven en
errors(el sync no aborta con excepción). - Errores de API: el cliente lanza
ApiError(status, detail)con eldetaildel backend (o"Request failed"); las páginas loguean en consola — solo Login muestra el error en pantalla. - Rate limiting y webhooks: la API incluye
core/rate_limit.pyy un router de webhooks (app/api/webhooks.py).