Preparando tu entorno

Estudio de desarrolloBrasil → Mundo

🎃 EVENTO DE HALLOWEEN · HASTA EL 31/10

Códigos mueven el mundo.Sitios deotro mundo.

MILWEB® · Desarrollo creativo · Brasil → Mundo

Proyectos / Loja de iPhone

COLECCIÓN DE RECOMPENSAS · 25 misiones

20 de 25

MISIÓN ESPECIALLoja de iPhoneLoja de iPhoneMW/OS · CASO 20 / 25E-commerce white-label · checkout por WhatsApp

20 de 25 · PRODUCTO DIGITAL

Loja de iPhone

E-commerce white-label · checkout por WhatsApp

PROYECTO PROPIO

Loja de iPhone — pantalla inicial
ESTUDIO
MILWEB
Tipo
Sistema Web / SaaS
Stack
Next.js · TypeScript · Tailwind · Supabase · Zustand

CONTEXTO

Las tiendas de iPhone venden solo por Instagram, sin una vitrina propia y profesional.

QUÉ SE HIZO

Tienda completa con catálogo, panel admin de productos e inventario, y pedido directo por WhatsApp. Es una base white-label que se convierte en varias tiendas cambiando solo color, logo y contacto.

1 BASE WHITE-LABEL → VARIAS TIENDAS

SOLUCIONES

  • White-label por config

    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.

  • Inventario por sucursal

    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.

  • Checkout 100% WhatsApp

    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.

  • RLS en toda tabla

    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.

DETALLE TÉCNICO

  • 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.

PANTALLAS

RESULTADO

Tienda completa con catálogo, panel admin de productos e inventario, y pedido directo por WhatsApp. Es una base white-label que se convierte en varias tiendas cambiando solo color, logo y contacto.

QUIERO ALGO PARECIDO ↗

Enter abre el primer resultado · Esc cierra · tecla / abre la búsqueda

Loja de iPhone — E-commerce white-label · checkout por WhatsApp | MilWeb