Loja de iPhone

- ESTUDIO
- MILWEB
- TIPO
- PRODUCTO DIGITAL
- ESTADO
- —
02 / LA IDEA
E-commerce white-label · checkout por WhatsApp
Las tiendas de iPhone venden solo por Instagram, sin una vitrina propia y profesional.
01 / 01
Un archivo (theme.config.ts) y variables de entorno definen marca, color y WhatsApp. Cada cliente es el mismo código con env vars distintas en Vercel.

02 / 02
Un trigger en Postgres suma el inventory de cada tienda física y recalcula products.stock automáticamente. La app nunca sabe si hay 1 o 10 tiendas.

03 / 03
Sin pasarela: número normalizado (regla del 9.º dígito), carrito reconciliado con el inventario real y selector de tienda cuando hay 2+ sucursales atendiendo.

04 / 04
Ningún uso de service_role; allow-list de admins vía función SECURITY DEFINER e IP de login con hash (SHA-256) para la LGPD.

01
02
03
04
04 / BAJO EL CAPÓ
01Next.js 15 (App Router) con TypeScript, Tailwind v4 con tema 100% CSS-first (sin tailwind.config: los colores viven en @theme inline en glob…+
Next.js 15 (App Router) con TypeScript, Tailwind v4 con tema 100% CSS-first (sin tailwind.config: los colores viven en @theme inline en globals.css) y Supabase cubriendo Auth, Postgres y Storage. La personalización multicliente no se hace por fork. Un único archivo (src/config/theme.config.ts) define nombre, WhatsApp, logo y paleta, que <ThemeInjector> convierte en CSS variables (--store-*) en el :root, y los componentes solo consumen utilidades (bg-bg, text-primary). Para reventa en serie, el mismo theme.config.ts lee variables NEXT_PUBLIC_STORE_* y NEXT_PUBLIC_COLOR_* con fallback: cada tienda nueva es el mismo código con Environment Variables distintas en Vercel. El único artefacto por cliente es el archivo del logo en /public.
02El inventario va más allá de un campo único: products.stock es siempre el total, pero dos tablas extra (units, inventory) habilitan un modo …+
El inventario va más allá de un campo único: products.stock es siempre el total, pero dos tablas extra (units, inventory) habilitan un modo multitienda física, con inventario por sucursal. Un trigger en Postgres suma el inventory en cada cambio y recalcula el total automáticamente, así que el resto de la aplicación (carrito, badge de inventario, JSON-LD) nunca necesita saber si la tienda tiene una o diez sucursales. Activar o desactivar ese modo siembra o limpia el inventario preservando el total, con una guarda contra condiciones de carrera que verifica que la sucursal recién creada sea de hecho la única, para que dos admins no dupliquen el inventario creando la 'primera' tienda al mismo tiempo.
03No hay checkout con pasarela: el carrito vive 100% en el cliente (Zustand con persist en localStorage) y el cierre es siempre por WhatsApp. …+
No hay checkout con pasarela: el carrito vive 100% en el cliente (Zustand con persist en localStorage) y el cierre es siempre por WhatsApp. Un método syncWithServer reconcilia el carrito guardado con el estado fresco de la base de datos en cada visita. Elimina ítems que quedaron inactivos o sin inventario, limita la cantidad al inventario disponible y marca lastSyncChanged para que la UI avise al cliente. Todo link wa.me pasa por normalizeWhatsapp(), que aplica la regla del noveno dígito de Anatel y el código de país 55, y por waUrl(), nunca armado a mano. El componente <WhatsappCta> decide solo el número correcto: va directo si la tienda tiene 0 o 1 sucursal, o abre un selector modal cuando hay 2 o más tiendas atendiendo, cada una con su propio WhatsApp.
04La postura de seguridad toma en serio que este es un white-label que se convierte en producto de terceros. RLS habilitado en toda tabla del …+
La postura de seguridad toma en serio que este es un white-label que se convierte en producto de terceros. RLS habilitado en toda tabla del schema público, sin excepción, con una allow-list de admins en una tabla sin ninguna policy, accesible solo vía la función SECURITY DEFINER is_admin(). La service_role no se usa en ningún lado: las mutaciones del admin corren con la sesión del dueño, con el RLS como defensa final. El rate limit de login persiste en Postgres, así que sobrevive al cold start serverless, y nunca guarda la IP en claro: almacena un SHA-256(salt + IP), por exigencia de la LGPD. Hasta el JSON-LD del producto escapa '<' como \u003c antes de serializar, para que un texto venido de la base con '</script>' no inyecte HTML en la página.
CONSTRUIDO CON — Next.js · TypeScript · Tailwind · Supabase · ZustandCÓDIGO ↗
06 / RESULTADO
1 base white-label → varias tiendas



Loja de iPhone Premium
