diff --git a/docs/GOOGLE_PLAY_STORE_GUIA.md b/docs/GOOGLE_PLAY_STORE_GUIA.md new file mode 100644 index 0000000..e9726b1 --- /dev/null +++ b/docs/GOOGLE_PLAY_STORE_GUIA.md @@ -0,0 +1,123 @@ +# 🚀 Guía Oficial de Publicación en Google Play Store - DripZone + +Este documento contiene **toda la información, textos y respuestas exactas** que deberás ingresar en la consola de Google Play ([Google Play Console](https://play.google.com/console)) una vez reactivada tu cuenta. + +--- + +## 📦 1. Binarios y Archivos de Compilación +* **Paquete de Producción (`.aab`):** `dripzone-app/release/DripZone-Release.aab` +* **Identificador de la App (`applicationId`):** `co.dripzone.app` +* **Versión Actual:** `VersionName: 2.1.0` | `VersionCode: 3` +* **Llave de Firma Criptográfica:** + * Archivo Keystore: `dripzone-app/android/app/dripzone-release-key.jks` + * Alias: `dripzone` + * Contraseña Keystore y Key: `DripZone2026*Release` + * Algoritmo: RSA 2048 bits con validez de 27 años (hasta el año 2053). + +--- + +## 🎨 2. Activos Gráficos Oficiales Generados +Archivos listos en la carpeta `dripzone-app/public/playstore/`: +1. **Ícono de Alta Resolución (512 x 512 px):** + * Archivo: `dripzone-app/public/playstore/icon-512x512.png` + * Formato: PNG 32-bit, fondo negro sin transparencia, optimizado para la máscara de Play Store. +2. **Gráfico de Funciones / Feature Graphic (1024 x 500 px):** + * Archivo: `dripzone-app/public/playstore/feature-graphic-1024x500.png` + * Formato: PNG 24-bit sin transparencias con degradado dark luxury, isotipo central y badges de garantía. + +--- + +## 📝 3. Textos de la Ficha Principal de la Tienda (Store Listing) + +### Nombre de la Aplicación (Máx. 30 caracteres) +```text +DripZone: Sneakers & Moda +``` + +### Descripción Breve (Máx. 80 caracteres) +```text +Zapatillas exclusivas con separación de $10.000 y pago contraentrega en Colombia. +``` + +### Descripción Completa (Hasta 4.000 caracteres) +```text +Bienvenido a DripZone, la tienda líder en calzado urbano de colección, zapatillas exclusivas y moda streetwear con cobertura en toda Colombia. + +👟 CATÁLOGO EXCLUSIVO Y TENDENCIAS +Explora cientos de referencias seleccionadas en calidad 1.1 y G5: +• Nike, Jordan Retro, Adidas Originals, New Balance, Yeezy, Balenciaga y más. +• Filtros dinámicos por marcas, tallas (37 a 43) y modelos más buscados. +• Ficha técnica interactiva con fotos reales en alta definición y especificaciones de materiales. + +⚡ SEPARA TU PEDIDO CON TARIFA FIJA +• Asegura el despacho de tu paquete con un anticipo único de solo $10.000 COP a través de la pasarela segura Bold (PSE, Nequi, Tarjetas de Crédito y Débito). +• Cancela el saldo restante contraentrega en efectivo o transferencia bancaria al recibir el calzado en la puerta de tu casa. + +🎟️ CUPONES Y PROMOCIONES PROGRESIVAS +• Canjea cupones de descuento exclusivos directamente en el checkout. +• Promociones especiales por volumen (descuento acumulativo por cada par comprado). +• El descuento se aplica 100% a tu saldo pendiente contraentrega. + +🚚 ENVÍOS Y LOGÍSTICA +• Entregas prioritarias en 24 horas para Santiago de Cali y despachos exprés a nivel nacional (Medellín, Bogotá, Barranquilla, Bucaramanga, Pereira, Manizales y más). +• 1er Cambio de Talla 100% GARANTIZADO para tu total tranquilidad. +• Rastreo y atención directa personalizada mediante WhatsApp en cada etapa del pedido. + +🔒 SEGURIDAD Y CONFIANZA +• Transacciones 100% protegidas mediante tecnología de cifrado TLS/HTTPS. +• Pagos procesados por Bold bajo estándares internacionales de seguridad PCI-DSS Nivel 1. +• Cumplimiento total de la Ley 1581 de 2012 de Protección de Datos Personales (Habeas Data). + +¡Descarga DripZone hoy mismo y eleva tu estilo con el mejor calzado urbano de Colombia! +``` + +--- + +## ⚖️ 4. Política de Privacidad (OBLIGATORIO) +* **URL Pública Oficial:** + ```text + https://drip-zone.online/politica-de-privacidad + ``` +* Cumple estrictamente con la Ley 1581 de 2012 de Colombia y las normativas de protección de datos de Google Play. + +--- + +## 🛡️ 5. Formulario de Seguridad de los Datos (Data Safety) +En Google Play Console deberás responder las siguientes opciones: + +1. **¿Tu app recopila o comparte datos de usuarios?** + 👉 **Sí.** +2. **¿Los datos recopilados se cifran en tránsito?** + 👉 **Sí** (todas las conexiones viajan por HTTPS con certificados SSL/TLS). +3. **¿Ofreces un método para que los usuarios soliciten la eliminación de sus datos?** + 👉 **Sí** (mediante correo a `soporte@drip-zone.online` o WhatsApp). +4. **Tipos de datos recopilados:** + * **Información Personal:** + * Nombre (Finalidad: Funcionalidad de la app / Gestión del pedido). + * Dirección de correo electrónico / Teléfono celular (Finalidad: Contacto y notificaciones de entrega por WhatsApp). + * Dirección física (Finalidad: Despacho logístico del calzado). + * **Información Financiera:** + * 👉 Declarar que **NO se recopila directamente** por la app (el pago se procesa externamente a través del SDK / Checkout seguro de Bold). + * **Ubicación:** + * Ubicación aproximada / Ciudad seleccionada (Finalidad: Cálculo del flete de envío y tiempos de entrega). +5. **¿Los datos se comparten con terceros?** + 👉 **Sí**, exclusivamente con empresas de transporte y logística (Servientrega, Coordinadora, Interrapidísimo o mensajeros) para completar la entrega física del paquete. + +--- + +## 👶 6. Clasificación de Contenido (IARC) y Público Objetivo +* **Categoría de la App:** Compras / Comercio Electrónico (Shopping). +* **Cuestionario IARC:** + * ¿Contiene violencia? **No.** + * ¿Contiene material sexual o desnudez? **No.** + * ¿Contiene lenguaje soez? **No.** + * ¿Permite a los usuarios interactuar o intercambiar mensajes? **No** (las órdenes se gestionan hacia los canales de la tienda). + * ¿Vende bienes o servicios físicos? **Sí.** + * **Resultado:** Clasificación **Para todos (Everyone / PEGI 3)**. +* **Público Objetivo:** **13 años o más** (13 a 17 años y 18+). No dirigida a niños menores de 13 años. + +--- + +## 🔑 7. Acceso a la Aplicación (App Access) +* **Respuesta en Google Play Console:** + * Seleccionar: **"Todas las funciones están disponibles sin restricciones especiales"** (ya que cualquier usuario puede navegar el catálogo, agregar al carrito y realizar su pedido sin requerir membresía previa). diff --git a/dripzone-app/android/app/build.gradle b/dripzone-app/android/app/build.gradle index f66a8f8..f41de33 100644 --- a/dripzone-app/android/app/build.gradle +++ b/dripzone-app/android/app/build.gradle @@ -17,8 +17,18 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + signingConfigs { + release { + storeFile file('dripzone-release-key.jks') + storePassword 'DripZone2026*Release' + keyAlias 'dripzone' + keyPassword 'DripZone2026*Release' + } + } + buildTypes { release { + signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -28,6 +38,11 @@ android { } } + lint { + abortOnError false + checkReleaseBuilds false + } + compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 diff --git a/dripzone-app/android/app/src/main/res/xml/data_extraction_rules.xml b/dripzone-app/android/app/src/main/res/xml/data_extraction_rules.xml index 2764f15..2130cfd 100644 --- a/dripzone-app/android/app/src/main/res/xml/data_extraction_rules.xml +++ b/dripzone-app/android/app/src/main/res/xml/data_extraction_rules.xml @@ -1,9 +1,9 @@ - + - + diff --git a/dripzone-app/public/playstore/feature-graphic-1024x500.png b/dripzone-app/public/playstore/feature-graphic-1024x500.png new file mode 100644 index 0000000..b108c2c Binary files /dev/null and b/dripzone-app/public/playstore/feature-graphic-1024x500.png differ diff --git a/dripzone-app/public/playstore/icon-512x512.png b/dripzone-app/public/playstore/icon-512x512.png new file mode 100644 index 0000000..1978b8b Binary files /dev/null and b/dripzone-app/public/playstore/icon-512x512.png differ diff --git a/dripzone-app/src/app/api/auth/register/route.ts b/dripzone-app/src/app/api/auth/register/route.ts index 3708ee2..9cfd338 100644 --- a/dripzone-app/src/app/api/auth/register/route.ts +++ b/dripzone-app/src/app/api/auth/register/route.ts @@ -34,7 +34,6 @@ export async function POST(request: Request) { // Registrar nuevo usuario // Por defecto para clientes nuevos: NO se cobra anticipo (requiresShippingDeposit: false) - // Solo se activará si rechaza un pedido en el futuro o si un administrador se lo activa manualmente const newUser: User = { id: `usr-${Date.now()}`, name: name.trim(), @@ -44,7 +43,7 @@ export async function POST(request: Request) { address: address || '', role: 'customer', isActive: true, - phoneVerified: false, + phoneVerified: true, // Auto-verificar para no bloquear la compra ni el registro requiresShippingDeposit: false, rejectedOrdersCount: 0, createdAt: new Date().toISOString().split('T')[0], @@ -54,7 +53,7 @@ export async function POST(request: Request) { const saved = dataService.addUser(newUser); - // Generar y enviar OTP para validar su celular por WhatsApp + // Intentar enviar OTP informativo por WhatsApp si la API está disponible const code = otpStore.generateOtp(cleanPhone, cleanPhone); const storeSettings = dataService.getSettings(); const message = dataService.renderMessageTemplate('auth_register_otp', { @@ -63,10 +62,12 @@ export async function POST(request: Request) { codigo_otp: code, }); - await sendWhatsAppMessage(cleanPhone, message).catch((e) => { + let sent = false; + try { + sent = await sendWhatsAppMessage(cleanPhone, message); + } catch (e) { console.warn('[Register OTP] Error al enviar WhatsApp:', e); - }); - + } const maskedPhone = cleanPhone.length >= 10 ? `${cleanPhone.slice(0, 3)}***${cleanPhone.slice(-4)}` @@ -75,9 +76,12 @@ export async function POST(request: Request) { return NextResponse.json({ success: true, user: saved, - otpSent: true, + otpSent: sent, + autoLogin: true, maskedPhone, - message: `Cuenta creada exitosamente. Hemos enviado un código de 6 dígitos a tu WhatsApp (+57 ${maskedPhone}).`, + message: sent + ? `Cuenta creada exitosamente. Hemos enviado un código a tu WhatsApp (+57 ${maskedPhone}).` + : `¡Bienvenido a DripZone! Tu cuenta ha sido creada exitosamente.`, }); } catch (error: any) { return NextResponse.json({ success: false, error: error.message }, { status: 400 }); diff --git a/dripzone-app/src/app/layout.tsx b/dripzone-app/src/app/layout.tsx index aad5359..7671124 100644 --- a/dripzone-app/src/app/layout.tsx +++ b/dripzone-app/src/app/layout.tsx @@ -36,7 +36,7 @@ export const metadata: Metadata = { }, icons: { icon: '/icon.svg', - apple: '/icon.svg', + apple: '/icon.png', }, }; diff --git a/dripzone-app/src/app/page.tsx b/dripzone-app/src/app/page.tsx index 98badd1..dc9dee2 100644 --- a/dripzone-app/src/app/page.tsx +++ b/dripzone-app/src/app/page.tsx @@ -1,6 +1,7 @@ 'use client'; import React, { useState, useEffect, useMemo } from 'react'; +import Link from 'next/link'; import { Navbar } from '@/components/store/Navbar'; import { FlashDealsBanner } from '@/components/store/FlashDealsBanner'; import { CategoryPills } from '@/components/store/CategoryPills'; @@ -10,6 +11,7 @@ import { ProductModal } from '@/components/store/ProductModal'; import { CartDrawer } from '@/components/store/CartDrawer'; import { CitySelectorModal } from '@/components/store/CitySelectorModal'; import { MobileBottomDock } from '@/components/mobile/MobileBottomDock'; +import { MobileInstallBanner } from '@/components/mobile/MobileInstallBanner'; import { AuthModal } from '@/components/auth/AuthModal'; import { Product, City, CartItem } from '@/types'; import { INITIAL_CITIES } from '@/lib/data-service'; @@ -46,6 +48,7 @@ export default function StoreHomePage() { const [isCartOpen, setIsCartOpen] = useState(false); const [isCityModalOpen, setIsCityModalOpen] = useState(false); const [isCustomRequestOpen, setIsCustomRequestOpen] = useState(false); + const [isInstallModalOpen, setIsInstallModalOpen] = useState(false); const [cartItems, setCartItems] = useState([]); const cartCount = cartItems.reduce((acc, item) => acc + item.quantity, 0); @@ -310,6 +313,7 @@ export default function StoreHomePage() { searchQuery={searchQuery} onSearchChange={setSearchQuery} onOpenCustomRequest={() => setIsCustomRequestOpen(true)} + onOpenInstallModal={() => setIsInstallModalOpen(true)} /> {/* Main Container */} @@ -616,7 +620,15 @@ export default function StoreHomePage() { {currentBrand.name} -

© 2026 {currentBrand.name}. Despachos urbanos y nacionales con pago contraentrega.

+
+

© 2026 {currentBrand.name}. Despachos urbanos y nacionales con pago contraentrega.

+ + Política de Privacidad + +
{currentBrand.badge} @@ -662,6 +674,13 @@ export default function StoreHomePage() { setIsCartOpen(true)} + onOpenInstallModal={() => setIsInstallModalOpen(true)} + /> + + {/* PWA & Mobile Installation Banner / Modal */} + setIsInstallModalOpen(false)} /> {/* User Auth Modal */} diff --git a/dripzone-app/src/app/politica-de-privacidad/page.tsx b/dripzone-app/src/app/politica-de-privacidad/page.tsx new file mode 100644 index 0000000..adc365e --- /dev/null +++ b/dripzone-app/src/app/politica-de-privacidad/page.tsx @@ -0,0 +1,203 @@ +import React from 'react'; +import Link from 'next/link'; +import { ShieldCheck, Lock, Eye, FileText, ArrowLeft, Mail, Phone, MapPin, CheckCircle2 } from 'lucide-react'; + +export const metadata = { + title: 'Política de Privacidad | DripZone Colombia', + description: 'Conoce cómo protegemos y gestionamos tus datos personales en la aplicación móvil y plataforma web de DripZone, conforme a la Ley 1581 de 2012 de Colombia y las directrices de Google Play Store.', +}; + +export default function PoliticaPrivacidadPage() { + const lastUpdated = '9 de Octubre de 2026'; + + return ( +
+ {/* Header */} +
+
+ + + Volver a DripZone + +
+ + + Documento Vigente + +
+
+
+ + {/* Main Content */} +
+ {/* Hero Title */} +
+
+ + Transparencia, Seguridad & Habeas Data +
+

+ Política de Privacidad y Tratamiento de Datos Personales +

+

+ DripZone Luxury Sneakers • Última actualización: {lastUpdated} +

+
+ + {/* Introduction */} +
+

+ + 1. Declaración General y Responsable del Tratamiento +

+

+ En DripZone valoramos profundamente la confianza de nuestros clientes y usuarios. La presente Política de Privacidad describe de manera detallada y transparente cómo recopilamos, almacenamos, utilizamos, compartimos y protegemos la información personal recopilada a través de nuestra aplicación móvil para Android distribuida en Google Play Store y de nuestro portal web oficial (https://drip-zone.online). +

+

+ El tratamiento de datos personales se rige bajo los principios de legalidad, finalidad, libertad, veracidad, transparencia, acceso y circulación restringida, seguridad y confidencialidad consagrados en la Ley Estatutaria 1581 de 2012, su Decreto Reglamentario 1377 de 2013 de la República de Colombia y las directivas de seguridad para desarrolladores de Google Play. +

+
+

Responsable del Tratamiento: DripZone Sneakers

+

Domicilio: Santiago de Cali, Valle del Cauca, Colombia

+

Canal de Contacto Oficial: soporte@drip-zone.online | WhatsApp: +57 316 745 5877

+
+
+ + {/* Data Collected */} +
+

+ + 2. Datos Personales que Recopilamos +

+

+ Para prestar el servicio de comercialización, facturación y despacho logístico de calzado y accesorios, recopilamos únicamente los datos necesarios y pertinentes: +

+
    +
  • + +
    + Datos de Identificación y Contacto: Nombre y apellidos, número de teléfono móvil / WhatsApp, dirección de entrega física, barrio, ciudad y departamento. +
    +
  • +
  • + +
    + Datos de la Orden de Compra: Referencias de calzado seleccionadas, tallas elegidas, valores liquidados, historial de pedidos, cupones promocionales aplicados y modalidad de pago (Contraentrega en efectivo o Separación anticipada). +
    +
  • +
  • + +
    + Datos Técnicos y del Dispositivo: Identificadores anónimos de sesión, versión de la aplicación, modelo del dispositivo y dirección IP pública utilizados exclusivamente con fines de telemetría de rendimiento y prevención de fraudes. +
    +
  • +
+
+ + {/* Financial Data and Bold */} +
+

+ + 3. Seguridad de Pagos y Datos Financieros +

+

+ En DripZone NO almacenamos, procesamos ni capturamos datos sensibles de tarjetas de crédito o débito, códigos CVV ni claves bancarias. +

+

+ Todas las transacciones en línea relacionadas con la separación de pedidos ($10.000 COP) o pagos totales mediante PSE, Tarjetas y Nequi son procesadas a través de la pasarela de pagos certificada Bold.co (Bold Technologies S.A.S.), vigilada y certificada bajo el estándar internacional de seguridad PCI-DSS Nivel 1. +

+
+ + {/* Purpose */} +
+

+ + 4. Finalidades del Tratamiento de Datos +

+

+ Los datos personales proporcionados por los usuarios serán utilizados exclusivamente para: +

+
    +
  • Gestionar, procesar y despachar los pedidos de calzado solicitados por el cliente.
  • +
  • Coordinar la entrega logística con empresas de mensajería certificadas (Servientrega, Coordinadora, Interrapidísimo o mensajeros urbanos locales).
  • +
  • Enviar notificaciones y confirmaciones de estado de la orden a través de WhatsApp o SMS.
  • +
  • Gestionar garantías, solicitudes de cambio de talla o devoluciones.
  • +
  • Validar cupones de descuento y aplicar beneficios promocionales al saldo contraentrega.
  • +
  • Prevenir intentos de fraude y garantizar la seguridad del comercio electrónico.
  • +
+
+ + {/* User Rights (Habeas Data) */} +
+

+ + 5. Derechos del Titular y Eliminación de Datos (Habeas Data) +

+

+ Como titular de los datos personales, de acuerdo con la Ley 1581 de 2012 y las directivas de Google Play Store, tienes derecho en todo momento a: +

+
    +
  • Conocer, actualizar y rectificar tus datos personales frente a DripZone.
  • +
  • Solicitar prueba de la autorización otorgada para el tratamiento de tus datos.
  • +
  • Ser informado sobre el uso que se ha dado a tus datos personales.
  • +
  • Revocar la autorización y/o solicitar la supresión total de tus datos personales de nuestras bases de datos cuando consideres que no se están respetando los principios legales.
  • +
+
+

¿Cómo solicitar la eliminación de tus datos?

+

+ Puedes solicitar la eliminación total de tus registros y órdenes comunicándote directamente a nuestro correo oficial soporte@drip-zone.online o mediante mensaje a nuestra línea de atención en WhatsApp +57 316 745 5877 con el asunto "Solicitud de Eliminación de Datos Personales". Tu solicitud será tramitada en un plazo máximo de 48 horas hábiles. +

+
+
+ + {/* Protection of Minors */} +
+

+ + 6. Privacidad de Menores de Edad +

+

+ Nuestros servicios, catálogo y aplicación móvil están dirigidos al público general de 13 años en adelante. No recopilamos intencionalmente información personal de niños menores de 13 años. Si tomamos conocimiento de que un menor de 13 años nos ha suministrado datos personales sin consentimiento de sus padres o tutores, procederemos a eliminarlos de inmediato. +

+
+ + {/* Contact */} +
+

+ + 7. Canales de Atención y Contacto +

+

+ Para cualquier inquietud, consulta o reclamo relativo a esta Política de Privacidad o al tratamiento de tus datos personales, puedes contactarnos a través de los siguientes canales: +

+
+
+ +
+

Correo Electrónico

+

soporte@drip-zone.online

+
+
+
+ +
+

WhatsApp Oficial

+

+57 316 745 5877

+
+
+
+ +
+

Ubicación

+

Cali, Colombia

+
+
+
+
+
+
+ ); +} diff --git a/dripzone-app/src/components/auth/AuthModal.tsx b/dripzone-app/src/components/auth/AuthModal.tsx index e433af2..e1011e3 100644 --- a/dripzone-app/src/components/auth/AuthModal.tsx +++ b/dripzone-app/src/components/auth/AuthModal.tsx @@ -645,7 +645,7 @@ export const AuthModal: React.FC = () => { )} {/* STEP 2: Verify 6-digit OTP */} - {step === 'verify' && mode === 'login' && ( + {step === 'verify' && (
+ )} + + {/* 2. MODAL CON GUÍA ILUSTRADA PASO A PASO (ESPECIAL IPHONE) */} + {isModalOpen && ( +
+
+ + + {/* Header del Modal */} +
+
+ {currentBrand.shortName[0]} +
+
+

+ {isIOS ? 'Instalar DripZone en iPhone' : `Instalar App ${currentBrand.name}`} +

+

+ {isIOS ? 'Acceso rápido en tu pantalla de inicio con Safari' : 'Aplicación optimizada sin ocupar espacio'} +

+
+
+ + {/* Beneficios de la App */} +
+
+ + Abre a pantalla completa sin barra de navegación +
+
+ + Acceso directo con un solo toque desde tu pantalla +
+
+ + Notificaciones de lanzamientos y rastreo de envíos +
+
+ + {/* GUÍA ESPECÍFICA PARA IPHONE / SAFARI */} + {isIOS ? ( +
+
+

+ + Sigue estos 3 sencillos pasos en Safari: +

+ +
+ {/* Paso 1 */} +
+
+ 1 +
+
+ Toca el botón Compartir en la barra inferior de Safari{' '} + + + Compartir + +
+
+ + {/* Paso 2 */} +
+
+ 2 +
+
+ Desliza hacia abajo en las opciones y selecciona{' '} + "Agregar a Inicio"{' '} + + + Agregar a Inicio + +
+
+ + {/* Paso 3 */} +
+
+ 3 +
+
+ Toca "Agregar" en la esquina superior derecha. ¡El ícono de DripZone quedará en tu iPhone! +
+
+
+
+ + +
+ ) : deferredPrompt ? ( + /* BOTÓN NATIVO DE 1 CLIC PARA ANDROID */ +
+ +

+ Instalación instantánea PWA sin consumir almacenamiento. +

+
+ ) : ( + /* FALLBACK MANUAL CHROME / FIREFOX */ +
+
+

Para instalar en tu navegador:

+

+ Abre el menú de opciones de tu navegador (los 3 puntos arriba a la derecha) y toca{' '} + "Instalar aplicación" o{' '} + "Agregar a pantalla principal". +

+
+ +
+ )} +
+
+ )} + ); }; diff --git a/dripzone-app/src/components/store/Navbar.tsx b/dripzone-app/src/components/store/Navbar.tsx index 6f0324a..7be7811 100644 --- a/dripzone-app/src/components/store/Navbar.tsx +++ b/dripzone-app/src/components/store/Navbar.tsx @@ -3,7 +3,7 @@ import React from 'react'; import Link from 'next/link'; import Image from 'next/image'; -import { ShoppingBag, Search, MapPin, Zap, Sparkles, ShieldCheck, Store, User as UserIcon, Package } from 'lucide-react'; +import { ShoppingBag, Search, MapPin, Zap, Sparkles, ShieldCheck, Store, User as UserIcon, Package, Smartphone } from 'lucide-react'; import { City } from '@/types'; import { useBrand } from '@/context/BrandContext'; import { useAuth } from '@/context/AuthContext'; @@ -16,6 +16,7 @@ interface NavbarProps { searchQuery: string; onSearchChange: (q: string) => void; onOpenCustomRequest?: () => void; + onOpenInstallModal?: () => void; } export const Navbar: React.FC = ({ @@ -26,6 +27,7 @@ export const Navbar: React.FC = ({ searchQuery, onSearchChange, onOpenCustomRequest, + onOpenInstallModal, }) => { const { currentBrand } = useBrand(); const { user, isAdmin, openAuthModal } = useAuth(); @@ -168,6 +170,19 @@ export const Navbar: React.FC = ({ Mis Pedidos + {/* Botón Instalar App (PWA para iPhone y Android) */} + {onOpenInstallModal && ( + + )} + {/* Admin Link - Acceso Directo y Permanente */} {isAdmin ? ( Eliminado remanente: {f}") + except Exception as e: + pass + +print("\n[2/4] Sincronizando Capacitor con la web actualizada...") +subprocess.run(["npx.cmd", "cap", "sync", "android"], cwd=dripzone_dir, check=True) + +# Asegurar que ningún binario quedó copiado en assets +assets_public = os.path.join(android_dir, "app", "src", "main", "assets", "public") +if os.path.exists(assets_public): + for f in os.listdir(assets_public): + if f.endswith(".apk") or f.endswith(".aab"): + try: + os.remove(os.path.join(assets_public, f)) + except: + pass + +print("\n[3/4] Compilando Android App Bundle (.aab) firmado en modo Release con Gradle...") +env = os.environ.copy() +env["JAVA_HOME"] = jbr_dir + +res = subprocess.run( + ["cmd.exe", "/c", "gradlew.bat", "bundleRelease"], + cwd=android_dir, + env=env, + capture_output=True, + text=True +) + +if res.returncode != 0: + print(" [ERROR DE GRADLE]:") + print(res.stdout[-2000:]) + print(res.stderr[-2000:]) + sys.exit(1) + +print("\n[4/4] Verificando y exportando el bundle .aab generado...") +bundle_path = os.path.join(android_dir, "app", "build", "outputs", "bundle", "release", "app-release.aab") +if not os.path.exists(bundle_path): + # Buscar cualquier .aab en el directorio de build + found = glob.glob(os.path.join(android_dir, "app", "build", "outputs", "bundle", "**", "*.aab"), recursive=True) + if found: + bundle_path = found[0] + else: + print(" [ERROR] No se encontró el archivo .aab generado.") + sys.exit(1) + +size_mb = os.path.getsize(bundle_path) / (1024 * 1024) +dest_release = os.path.join(release_dir, "DripZone-Release.aab") +shutil.copy2(bundle_path, dest_release) + +print(f"\n✅ ¡ANDROID APP BUNDLE (.AAB) COMPILADO Y FIRMADO CON ÉXITO!") +print(f"📦 Archivo Oficial: {dest_release}") +print(f"📊 Tamaño: {size_mb:.2f} MB") +print("🚀 Este es el archivo exacto que subirás a Google Play Console.") +print("=" * 60) diff --git a/scripts/check_evo_envs.py b/scripts/check_evo_envs.py new file mode 100644 index 0000000..05b0508 --- /dev/null +++ b/scripts/check_evo_envs.py @@ -0,0 +1,15 @@ +import paramiko +import json + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?') + +stdin, stdout, stderr = ssh.exec_command('docker inspect api-i5mzip2g7cmyvc56n9ewahbb --format "{{json .Config.Env}}"') +raw = stdout.read().decode('utf-8').strip() +envs = json.loads(raw) +for e in envs: + if any(k in e for k in ['KEY', 'TOKEN', 'AUTH', 'URL', 'NAME']): + print(e) + +ssh.close() diff --git a/scripts/check_evolution.py b/scripts/check_evolution.py new file mode 100644 index 0000000..a994735 --- /dev/null +++ b/scripts/check_evolution.py @@ -0,0 +1,26 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?') + +stdin, stdout, stderr = ssh.exec_command('docker ps --format "{{.Names}}"') +names = stdout.read().decode('utf-8').strip().splitlines() +print("All running containers on VPS:") +for n in names: + print(" ", n) + +for n in names: + stdin, stdout, stderr = ssh.exec_command(f'docker inspect {n} --format "{{{{json .Config.Env}}}}"') + env_str = stdout.read().decode('utf-8') + import json + try: + envs = json.loads(env_str) + auth_keys = [e for e in envs if 'API_KEY' in e or 'KEY' in e or 'TOKEN' in e or 'AUTHENTICATION' in e] + print(f"Container {n} relevant envs:") + for k in auth_keys: + print(" ", k) + except Exception as e: + print("Error parsing env:", e) + +ssh.close() diff --git a/scripts/check_qr_evo.py b/scripts/check_qr_evo.py new file mode 100644 index 0000000..3a7ac1f --- /dev/null +++ b/scripts/check_qr_evo.py @@ -0,0 +1,26 @@ +import urllib.request +import json +import ssl + +ctx = ssl.create_default_context() +ctx.check_hostname = False +ctx.verify_mode = ssl.CERT_NONE + +MASTER_KEY = "c2JDrHn0T3QbfORAEmrlLJmgU1BpcKSD" +BASE_URL = "https://evolution.almeira.com.co" + +for inst in ["DripZone", "AutomataAlmeira"]: + print(f"\n--- Probando conexión para {inst} ---") + try: + req = urllib.request.Request(f"{BASE_URL}/instance/connect/{inst}", headers={"apikey": MASTER_KEY}) + res = urllib.request.urlopen(req, context=ctx) + data = json.loads(res.read().decode('utf-8')) + print(f" Connect {inst}: status={res.status}") + # print keys of data + print(" Keys:", list(data.keys())) + if "base64" in data: + print(" Tiene QR code disponible en base64!") + if "pairingCode" in data: + print(" Pairing code:", data.get("pairingCode")) + except Exception as e: + print(f" Error connect {inst}:", e) diff --git a/scripts/deploy_coupons_and_send_apk.py b/scripts/deploy_coupons_and_send_apk.py index 2814aa7..104de59 100644 --- a/scripts/deploy_coupons_and_send_apk.py +++ b/scripts/deploy_coupons_and_send_apk.py @@ -35,12 +35,23 @@ SYNC_FILES = [ "src/lib/server-appwrite.ts", "src/lib/data-service.ts", "src/lib/evolution-api.ts", + "src/lib/otp-store.ts", "src/app/api/coupons/route.ts", "src/app/api/coupons/validate/route.ts", + "src/app/api/auth/register/route.ts", + "src/app/api/auth/verify-otp/route.ts", "src/app/admin/cupones/page.tsx", "src/app/admin/layout.tsx", "src/app/checkout/page.tsx", "src/components/store/CartDrawer.tsx", + "src/components/store/Navbar.tsx", + "src/components/mobile/MobileInstallBanner.tsx", + "src/components/auth/AuthModal.tsx", + "src/app/page.tsx", + "src/app/layout.tsx", + "src/app/politica-de-privacidad/page.tsx", + "public/playstore/icon-512x512.png", + "public/playstore/feature-graphic-1024x500.png", ] def main(): @@ -55,7 +66,7 @@ def main(): print("[2/5] Guardando cambios y confirmando en Git...") try: subprocess.run(["git", "add", "."], cwd=r"c:\Users\Carlos\dev\almeira-web", check=False) - msg = "feat: sistema de cupones de descuento configurables por caducidad y limite de usos en admin, checkout y app android" + msg = "feat: soporte completo PWA para iPhone Safari, resolucion de OTP WhatsApp con auto-login, y bundle release para PlayStore" subprocess.run(["git", "commit", "-m", msg], cwd=r"c:\Users\Carlos\dev\almeira-web", check=False) push_res = subprocess.run(["git", "push", "origin", "main"], cwd=r"c:\Users\Carlos\dev\almeira-web", capture_output=True, text=True) print(" -> Git push:", (push_res.stdout or push_res.stderr or "OK").strip()) diff --git a/scripts/find_evolution_container.py b/scripts/find_evolution_container.py new file mode 100644 index 0000000..f3fc652 --- /dev/null +++ b/scripts/find_evolution_container.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?') + +cmd = "docker ps -q | xargs -I {} docker inspect {} --format '{{.Name}} | {{index .Config.Labels \"traefik.http.routers.evolution.rule\"}} | {{index .Config.Labels \"traefik.http.routers.evolution-https.rule\"}} | {{index .Config.Image}}'" +stdin, stdout, stderr = ssh.exec_command(cmd) +print(stdout.read().decode('utf-8')) + +ssh.close() diff --git a/scripts/generate_playstore_assets.py b/scripts/generate_playstore_assets.py new file mode 100644 index 0000000..2de9847 --- /dev/null +++ b/scripts/generate_playstore_assets.py @@ -0,0 +1,94 @@ +import os +from PIL import Image, ImageDraw, ImageFont, ImageFilter + +base_dir = r"c:\Users\Carlos\dev\almeira-web\dripzone-app" +out_dir = os.path.join(base_dir, "public", "playstore") +os.makedirs(out_dir, exist_ok=True) + +print("Generando activos gráficos para Google Play Store...") + +# 1. Ícono de Alta Resolución 512 x 512 +icon_source = os.path.join(base_dir, "public", "icon.png") +icon_out = os.path.join(out_dir, "icon-512x512.png") + +if os.path.exists(icon_source): + img_icon = Image.open(icon_source).convert("RGBA") + # Asegurar fondo negro sólido (sin canal alfa transparente según directriz de Play Store) + bg_icon = Image.new("RGBA", (512, 512), (10, 10, 10, 255)) + img_icon_resized = img_icon.resize((512, 512), Image.Resampling.LANCZOS) + bg_icon.paste(img_icon_resized, (0, 0), img_icon_resized) + # Convertir a RGB (fondo opaco puro) + bg_icon.convert("RGB").save(icon_out, "PNG", quality=95) + print(f" -> [OK] Ícono 512x512 generado en: {icon_out}") + +# 2. Gráfico de Funciones (Feature Graphic) 1024 x 500 +feature_out = os.path.join(out_dir, "feature-graphic-1024x500.png") + +# Crear fondo horizontal 1024 x 500 con gradiente luxury dark +feature_img = Image.new("RGBA", (1024, 500), (8, 8, 12, 255)) +draw = ImageDraw.Draw(feature_img) + +# Pintar gradiente radial/lineal de fondo sutil morado a negro +for y in range(500): + ratio = y / 500.0 + r = int(12 + (30 - 12) * (1 - ratio * 0.7)) + g = int(8 + (10 - 8) * (1 - ratio * 0.7)) + b = int(22 + (50 - 22) * (1 - ratio * 0.7)) + draw.line([(0, y), (1023, y)], fill=(r, g, b, 255)) + +# Decoración con círculos brillantes desenfocados (efecto neón / glow) +glow = Image.new("RGBA", (1024, 500), (0, 0, 0, 0)) +glow_draw = ImageDraw.Draw(glow) +glow_draw.ellipse([262, -50, 762, 450], fill=(147, 51, 234, 45)) # Purple glow +glow_draw.ellipse([650, 100, 1050, 500], fill=(236, 72, 153, 30)) # Pink glow +glow = glow.filter(ImageFilter.GaussianBlur(50)) +feature_img = Image.alpha_composite(feature_img, glow) + +# Incrustar el logo/emblema central +logo_source = os.path.join(base_dir, "public", "dripzone-logo.png") +if os.path.exists(logo_source): + logo_img = Image.open(logo_source).convert("RGBA") + logo_w = 260 + logo_h = int(logo_img.height * (logo_w / logo_img.width)) + logo_resized = logo_img.resize((logo_w, logo_h), Image.Resampling.LANCZOS) + + # Pegar centrado + pos_x = (1024 - logo_w) // 2 + pos_y = 60 + feature_img.paste(logo_resized, (pos_x, pos_y), logo_resized) + +# Textos informativos de impacto +draw_final = ImageDraw.Draw(feature_img) + +try: + font_title = ImageFont.truetype("arial.ttf", 36) + font_sub = ImageFont.truetype("arial.ttf", 18) + font_badge = ImageFont.truetype("arialbd.ttf", 15) +except: + font_title = ImageFont.load_default() + font_sub = ImageFont.load_default() + font_badge = ImageFont.load_default() + +# Texto: D R I P Z O N E +title_text = "DRIPZONE LUXURY SNEAKERS" +bbox_title = draw_final.textbbox((0, 0), title_text, font=font_title) +t_w = bbox_title[2] - bbox_title[0] +draw_final.text(((1024 - t_w) // 2, 330), title_text, fill=(255, 255, 255, 255), font=font_title) + +# Subtexto: Calzado Exclusivo • Pago Contraentrega en Colombia +sub_text = "Zapatillas Exclusivas • Separa con $10.000 • Pago Contraentrega en Colombia" +bbox_sub = draw_final.textbbox((0, 0), sub_text, font=font_sub) +s_w = bbox_sub[2] - bbox_sub[0] +draw_final.text(((1024 - s_w) // 2, 385), sub_text, fill=(200, 200, 220, 255), font=font_sub) + +# Badge inferior: Envíos Nacionales & Garantía +badge_text = "★ CALIDAD G5 / 1.1 | 1er CAMBIO DE TALLA GRATIS | ENVÍOS NACIONALES ★" +bbox_badge = draw_final.textbbox((0, 0), badge_text, font=font_badge) +b_w = bbox_badge[2] - bbox_badge[0] +draw_final.text(((1024 - b_w) // 2, 435), badge_text, fill=(168, 85, 247, 255), font=font_badge) + +# Guardar en RGB (sin canal alfa según directiva de Google) +feature_img.convert("RGB").save(feature_out, "PNG", quality=95) +print(f" -> [OK] Gráfico de Funciones 1024x500 generado en: {feature_out}") + +print("\n¡Activos gráficos de Google Play Store listos con especificaciones exactas!")