MILWEB®
MILWEB®
MW/024LOJA DE IPHONEESTUDIO

Loja de iPhone

Loja de iPhone — pantalla inicial
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.

03 / EXPERIENCIA
  1. 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.

    Loja de iPhone — 01
  2. 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.

    Loja de iPhone — 02
  3. 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.

    Loja de iPhone — 03
  4. 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.

    Loja de iPhone — 04
Home de la tienda con hero animado y catálogo de destacados

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 CONNext.js · TypeScript · Tailwind · Supabase · ZustandCÓDIGO

06 / RESULTADO

1 base white-label → varias tiendas

[ QUIERO ALGO PARECIDO ]

07 / PANTALLAS SELECCIONADAS
Home de la tienda con hero animado y catálogo de destacados
PANTALLA 01Home de la tienda con hero animado y catálogo de destacados
Sección de catálogo con inventario, condición y cuotas por producto
PANTALLA 02Sección de catálogo con inventario, condición y cuotas por producto
Sección de tiendas físicas con dirección, horario y WhatsApp por sucursal
PANTALLA 03Sección de tiendas físicas con dirección, horario y WhatsApp por sucursal
SIGUIENTE EXPERIENCIA18 / LOJA DE IPHONE PREMIUM

Loja de iPhone Premium

Loja de iPhone Premium

TODOS LOS PROYECTOS

TODOS LOS PROYECTOS