From 06ee114953cff23df5e064dfe0b504b92409150c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 30 Jun 2026 00:27:36 +0000 Subject: [PATCH] Auto commit: 2026-06-30T00:27:36.927Z --- config/__pycache__/__init__.cpython-311.pyc | Bin 159 -> 159 bytes config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5620 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1663 bytes config/__pycache__/wsgi.cpython-311.pyc | Bin 679 -> 679 bytes config/settings.py | 3 + config/urls.py | 1 + core/__pycache__/__init__.cpython-311.pyc | Bin 157 -> 157 bytes core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 3235 bytes core/__pycache__/apps.cpython-311.pyc | Bin 524 -> 524 bytes .../context_processors.cpython-311.pyc | Bin 763 -> 763 bytes core/__pycache__/forms.cpython-311.pyc | Bin 0 -> 4062 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 7029 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 762 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 6301 bytes core/admin.py | 39 ++- core/forms.py | 59 +++++ core/migrations/0001_initial.py | 84 ++++++ core/migrations/0002_seed_tech_store.py | 65 +++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 5291 bytes .../0002_seed_tech_store.cpython-311.pyc | Bin 0 -> 5204 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 168 -> 168 bytes core/models.py | 100 +++++++- core/templates/base.html | 59 ++++- core/templates/core/catalog.html | 32 +++ core/templates/core/confirm_payment.html | 43 ++++ core/templates/core/index.html | 239 ++++++++---------- .../templates/core/partials/product_card.html | 18 ++ core/templates/core/payment_success.html | 26 ++ core/templates/core/product_detail.html | 46 ++++ core/urls.py | 6 +- core/views.py | 135 ++++++++-- static/css/custom.css | 216 +++++++++++++++- staticfiles/css/custom.css | 225 +++++++++++++++-- 33 files changed, 1212 insertions(+), 184 deletions(-) create mode 100644 core/__pycache__/forms.cpython-311.pyc create mode 100644 core/forms.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_seed_tech_store.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_seed_tech_store.cpython-311.pyc create mode 100644 core/templates/core/catalog.html create mode 100644 core/templates/core/confirm_payment.html create mode 100644 core/templates/core/partials/product_card.html create mode 100644 core/templates/core/payment_success.html create mode 100644 core/templates/core/product_detail.html diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 896bb4f1b8949c96d2f1bf743293342b70a9d0e4..152c8c036857cf9316b90d19ca3ce533deefd81b 100644 GIT binary patch delta 20 acmbQwIG>ScIWI340}ya=IcH7enG66b2?PlM delta 20 acmbQwIG>ScIWI340}#~ttjL_mGZ_Fdwgn>q diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index d79d6a79890a58443ee5f65d8d558823a19e9aed..8e885111bed6dd289afbf84782cffabdf3d11c8a 100644 GIT binary patch delta 561 zcmdm>{Y9I1IWI340}zCAIcHT* zZ33dxnWI#g7*d(Dz(#HHfR<2Zd?T|6D*LxX&7aY2|t{{A7i1cE~x zLp+_`Jbhe)<6S(1f{RRm_7<4}iCfH>c_l?=Ag%?7umllSAi^3%*aC@Lti=U6nI%Pz tlluh?xiWzwzyM|_e!KaU;6x_=2Rxz;oHuwyXRu%7m2cqM94!*c2mn2bYwrL6 delta 469 zcmeyOy+NCIIWI340}#~ttjKhk$ScWsWuy94rutOQ6oV>m1_mH)2&IicbUJgC5)(rz za~4<=NMnj|iper228Pu@3;|KfXevxoxX@In09B}>sWMA3hpU?0$t)78hNi$Gg%_>> zY*UmvnhHw{6&h$NtR{Z571l%(v`(>^_`{k}Yq9~0f<}~fihT;&!FHUW1wG(On}5K=FGg3B2y6897I@v2ul!Q1tM&K#4Xn1 if}G5fB8SOG1P!^;fFg`QTzqfyf5C}No9By!G6DcSBUvB- diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 8cf22af743397374cabf5fa358c4f52631471db9..4d96c85b75a3371d5925fa9e455d97cde84e0ec2 100644 GIT binary patch delta 165 zcmbQr^Ph)zIWI340}zCAIcL>QmSvsK%*+@#c|WV8 y$qg3%31$<_W@ucI(%Ip0#m4`FaKJ_3fGa?fC7{6@1V69?6*GQd0FgyfKr;Z+@G3F@ delta 92 zcmey*GnI#TIWI340}#~ttjN@v$ScXnHBo&5qshb*DVq}*e={)(PL5$&Fw diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index a1b4aa75cef92df0f6f3c8f77e2be44ec1b36534..87f06e14ffb6689270fcc58d08d033e0baf8cb85 100644 GIT binary patch delta 21 bcmZ3^x}23~IWI340}ya=IcII;naup=fZd7*11VYw{MJ&M~uAm75)f>12yCy&m3ih_p&9VLMrFnb=%UA#%{?nXMQk*7&q8ILP@)MI(L_to5|*eX zkqcYUBumm{ONP}be5g&`bhN4@PZkZA@}%#Ci@eMBb;Atohm@y$j|IBNEXuMX>w&MZ zUW7EjQ-O6u0sUC;Zj1URvrD-AS@@Ew>!?%Lc}mw^*o=eqw5~r04W}`Zc5FY;E!!_S z#xn1TTGX-wW5J<(u*7J|D}@g1+|u(lbu6Fv_|#x#q0xLwcHz_H^`%_Fb7?MI2#Z0O zqu)_83_O;5>yjzOglwE8pBYd1m9& z%85&rVp+!QRD{We|`i=s4BV5mCna;-QGJBYpwN z-A5bihvC_*%8uaF0fa$>ApqVTv6Hu*fHHOzyN@9pM;JzkOmhNj?=%5F=U)QYZ{G#5 z@4#o}ueXM#+Kl|gej}rj79%%=nbGplbxi6uX83ptj$MmmI){d2kw z%t;-X(;d4wAMW76F{ujgF+)Fq!^t9V;7Y0C*Ro54f@g8ra6LG+yo=F1WwdBgF8cvc z>+_V!fMYoOMgVla54&a;5H12_lk7YcymwE1sQ@u$Uz@sy?XWD$3h@+?_Zubn{zIyl z;*jI>K1P?Q8SHheoSX)U1%qWu#-itEeCkls1eZ1}&j&?#Ld+%;P$v@nvAz2%z&cqK zH=sP2f@Y|Kp{2cgfDYJ5OUY)fm6_+lCdfbAoAZ5Qm2BWw52RILHSr5vMZbxyGw^}} zhfB;p6(cM1Zbl#4^igEl@HFVjn2p$L+JF909HV|VV?<`Kj4?kuo|U;AmMq|c$so=9 z?%bWed+&}uJwJbY?*7#;87`eCiryohcB$_hi&PJN%AP`81q0@}!svsL`T7CR;X!( z$GPf&T^q1}P`1?c-|EO;>d2D|8;h0E**I6#xtcmxY39g}SKhfz&4RJg`5gRj4*>fR zOckQhIxx=S9XBawAAwja-?y(`zdCcb!8e=0@yiIG0JQO#gRYgaLwRiTI&|g#OygW- z6mwObuc`BuW{zl_&89oWa&{J1jgcGuA3VG0EH^J$1#f<@=reR`h)z+9;jv`6r}j{P z4*L-B3h;jd*dOW(80tU9P=6mn{ju}qxvk!b^0jU0NcrB{H!K*l z3w46HlOXbyH+?FR2V(Kbigcwa!O#^HzZHq!io~bd`cTm=A-%2h@e;k7~;Pu)a;D8R>0iRiwIEYW%q zzE&sWhtCxI3RK{G&%X>D9j8TX{tM&v3H<;7 delta 168 zcmZ21d4(}$IWI340}#~ttjM$n(vLwL7+``jJ_`XE(-~42QW$d>av7r-85vTTf*CZK zUxE~9GTvfMOv%m6^V4Ly#g~$mn3tZfmz0#kn&dbZi00dlI&RGk2m;fh2184vM delta 20 acmeBS>0#kn&dbZi00ebDD>4`IFaZE9?*xJX diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf2234fb21a6b62efc5cec11af9512dd0c9616..78e13ffd2d055d21d00a76a37aa7df6eb40b4d48 100644 GIT binary patch delta 21 bcmey(`kR$!IWI340}ya=IcII;dCvp@K12ma delta 21 bcmey(`kR$!IWI340}xbw%g@}%^PUL+NjnD4 diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c57a000295dc3c2cbc11875bee6be14bca7c8af4 GIT binary patch literal 4062 zcmbssTWk~Ab!PmG^G=MNbT{slLW2#ldG0P`8y*RCslXc*_EC42*~#3SWWso^xicoj zo2^iVw5Fn~w4x#(`ADmZ!h*C&{XzYz#Ls??(MXI&TB%aM_%jM06<cduL~igLXJJK39U#f7qnaLhFOI2^ieq5Qlh;EAj=N=a7H`notZB z0z4;hzu-{t3Wq|Np9?lVz8T=SJMj3e5JKTITsrcYP1C#^nkKYpJm&pb|D?Q7#JV|7 z^l6nAWm6@(;(zGF231dzFF_W}6?nuI1dlflyoH+r@Po&1g+P^?=275Ev=C&n0sj!d zaFx5n7a|}@*iF8I=?Rdkg>mOPy|u4E)aPt*pb(q^b^Mhtgai(U=OPWpOff2dH+U29 z@37-}&BKW{W(bslA}hl8VnsK_YTH1oZBs1Iv~=M$rCFkHoa3UbYHnCAlCo~P;UYF?33A&gp2igG z3U-4!F|iw=Six$^bVG_J8%Cv_x$}sDHLRG-yJgd)My11BRfujq^r8y#iU!f(Z30h_FdgvO1}T|0JdTzA zNnhgx&e-_K4d=aMm*c?cEY=>}o+dg0Nj{pDP2*^(ROub}PAcvoC19ebu=Y{bhm6{g zVGkKjYGfI&ko&szDYo`cv!0r%rDp8ZjFZYPV>^|-|6A)EwGJ4pr_5T)v{R;&7+%75 zV)%a7w}$n}RGs5Y{+Ix--1u{toDiOH9Kfd@bP`*a@OACh@V%c~>7!QSSS@kPP8@Rv zcPwvN`SJaf^>NwCU1WW5zBV{-56(OBtxLzR=kJYuy9Y?0<3D7yGao&NTRvtS00IYq zPBOPtwv)Mg^Z)9y-g@6kPS%o>c5<@bAM0nD>W@_;Oi3fo&~_)a-%0Ip65HS?&ftg> zPs7)MxUSM_nEhx5bWe8yP?`ZBCd`YN7q5Xh+@JXykSEOz`!`|r4m^IFYwj%aDz_;7 zjsFU=>>@YU%xH)oY#?)U&AhtW$c)WBHbBDNKx+b8ZRGM+ycg2cX8fckKNQ*&C)^;7 zIF?`Ylx3f@5DnW@Ly{F!y@=hYBAXET=t3poT_x|fi)FnTE7B1+WRxLXv5TtestMOo z1Uhc?LK)Kq14B@rRy7k4y8Lh}QLF(`Y2C+8n24y+}m+bt!_imFU zFJ4QM!Sp7I;$^xM26PXAinv)eX-L9Q4IRLLFLO?G;18jzk!uk~wicp@iwBK?TW(bx8*A4-=!Og&FJ{|Z1 z@K<5}a%+|To5UjjN8#Gw8IGm_7o07k8c3Z{&9vYO+1+jsvKTekB@#u?)X!q?YN}yE zE-j5!j%;Re1tPXCwN?q)cQvBOnlT2028F#q*w_x>GH3OU`h9u-t%+|=eSPY7{%#&t zmbNu_JVQQaWng(ffa8Wj)hW!}-)v^Zix2*ysgU!$j8=jaL`7CW!2OY_iL~ucdvpvBes7hGNV z9#?-M(yqIDFi(3xGRlH>C3i~RP0#-M%4z`sw5&rlPL)qxgUw-@ba@j_-XGf9GNRom%{m9Y0iU zWA<3MI(BVr`RK#;A*+3;VUb_g`U-Ym!RjbD-2+y1z%xlYO5bEM4l$5ofTck%oYME$ z=$8Q8&h@aXU`>;xG*1u0s=0iNd_Zoq2o|K^6R)?N=E;6c)t0V~O#u=S^} I*z^SWAF!7&-6<+@Ti$9_y+VT%&OL8Q~lI)v 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 cvxf4*fDLE&`SL;kA76gTbav7r-85vTTf*CZK zUxE~9GTvg#%}+_qDfZK3y2Y82m6(^Fua}Zk#0-?2e39AQ>KBI%P(f)d0X Xi)Det2WCb_#v2SW7qFosHlPXs;h-c8 diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index f705988a223abc7b5af333902925bc196186bbf5..ee47045d5e7e2e2b1bf087eb63b36b08a67cb3a4 100644 GIT binary patch literal 762 zcma)2y>HV%6u*m~?KmM#Rfz?V0%N3NODvVDs7oUe7C?Mt@DjPc!*%QPS!cVYnL2i4 zqhlvN1S10r|HDPfPK3V6C(gzhKpA-Vp}XJjz4v?Xee85vNZIq&?!ga?(692d zG^>7Ge^Zb5h#-O^#ITDoA`PNN4W_vo0~eU?>dbHrX1XRu4MZ^ksuTUI@j*lAi#mU- zVoa)$=1Rt*CTXo?+7!sGl}v{kWbI!j(A_5KzC``?t5Ud+3rM_d|4KE8`g&pdUgkwS zD7rq6--Loq67Pi3I4fYEGg@@(?DqB@t#>B<3`TPp r)##n`XY$dmgy9T^a~Rg><|i(PuO*CUFrLGB(Kfn`({{PUx+?xR3+U2n delta 215 zcmeyxdYh?!IWI340}#~ttjNp)(vLwL7+{4mKHC5p(-~42QW$d>av7r-85vTTQkZj? za+#x;85x)uQW;ZNQkhd&*RU;PW?)zi#1N3q7{!vp9?YQ0@e(AU$#{#UAh9IlB_ouR zA)23?dW)e5WD!U*FEKaOPm^`>Mn<{Gj~RWr_<-UdwZ&eOt(as4#4j+QpviSiVgetS PnfRF+xIwUp2dEYR1ga}F diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2f0989c7137f7930a63ea76fcd63516c4974e834..6705550d4c9592b72d7f33711a99b4f8aae6ed0e 100644 GIT binary patch literal 6301 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 literal 1364 zcmZ`(&1)M+6ra_{dS%J>I!@|TuH%hUh$T{~CE!96oTjQ1+$K)2Q%ZHHP<`ZWm8k%0`!Liw{@%1fY!tdJefM7AQl zBhn19Sa6Ea_&F0f52+#t(zs7E+?3c%aE4Gt31lN`$cK>CaWDY%0l$fN{S4(m*^98b zAQFOZ8L?*M5b8J{)dItqY^-hhUDsF{YgW-QOsAxII>p%0Y9=dd7Ae9|G&s?)vtv@? z*w|s3fmzYCyi6leblsZSXw)-0vAJ^Fb$=8ZkKM6~%pxUIZ^SlnP1|O53-h^$TMxcl z&pj%9yS|;<+IaYARqQc33p$+=>?7eip%aJUUFM01nxMCzWy{3I+3Mq}!>U=li}flaG+QI|spl4T zob~hDF>%eyxOE;8-gFVJ3n+Sj0BfPw>ijEJJ5jZcy4+QlU#Tl6>PkmVchz)jqo<{g zem>D|w6z=i>HYM}h1U1|$#c)H{hoQ2Y2^ko`uNk{{Bl3J(n+Sf$#g%t+)aMbNnYMq_I%*JNq0Rq;#`{q1K zoZG+JUbuEt>cm&O@zr)@^%Rl>0Md)F(RpB8KAS2P06puedS=OZWkMOW!1G(l6GL;A z0R;Uy__hCq=p}LW6JT$Zn>I137G9%QKox%h?+LI$Sdye3inaW&hh|#-_ZofP9$!6l Xr9HlS=yH2}4P;gNa)91vejfh;`i@)& diff --git a/core/admin.py b/core/admin.py index 8c38f3f..5a04705 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,40 @@ from django.contrib import admin +from django.utils import timezone -# Register your models here. +from .models import PaymentConfirmation, Product, ProductCategory + + +@admin.register(ProductCategory) +class ProductCategoryAdmin(admin.ModelAdmin): + list_display = ("name", "slug", "is_active", "sort_order") + list_editable = ("is_active", "sort_order") + prepopulated_fields = {"slug": ("name",)} + search_fields = ("name", "description") + + +@admin.register(Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ("name", "category", "price_from", "delivery_time", "is_featured", "is_active") + list_filter = ("category", "is_featured", "is_active") + list_editable = ("price_from", "is_featured", "is_active") + prepopulated_fields = {"slug": ("name",)} + search_fields = ("name", "short_description", "description") + + +@admin.register(PaymentConfirmation) +class PaymentConfirmationAdmin(admin.ModelAdmin): + list_display = ("customer_name", "product", "method", "amount", "reference", "status", "created_at") + list_filter = ("status", "method", "created_at") + search_fields = ("customer_name", "customer_phone", "customer_email", "reference") + readonly_fields = ("created_at",) + actions = ("approve_payments", "reject_payments") + + @admin.action(description="Aprobar pagos seleccionados") + def approve_payments(self, request, queryset): + updated = queryset.update(status=PaymentConfirmation.STATUS_APPROVED, reviewed_at=timezone.now()) + self.message_user(request, f"{updated} confirmación(es) aprobada(s).") + + @admin.action(description="Rechazar pagos seleccionados") + def reject_payments(self, request, queryset): + updated = queryset.update(status=PaymentConfirmation.STATUS_REJECTED, reviewed_at=timezone.now()) + self.message_user(request, f"{updated} confirmación(es) rechazada(s).") diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..1418d06 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,59 @@ +from django import forms + +from .models import PaymentConfirmation + + +class PaymentConfirmationForm(forms.ModelForm): + class Meta: + model = PaymentConfirmation + fields = [ + "product", + "customer_name", + "customer_phone", + "customer_email", + "amount", + "method", + "reference", + "note", + "receipt", + ] + widgets = { + "product": forms.Select(attrs={"class": "form-select"}), + "customer_name": forms.TextInput(attrs={"class": "form-control", "placeholder": "Tu nombre o negocio"}), + "customer_phone": forms.TextInput(attrs={"class": "form-control", "placeholder": "+53..."}), + "customer_email": forms.EmailInput(attrs={"class": "form-control", "placeholder": "correo@ejemplo.com"}), + "amount": forms.NumberInput(attrs={"class": "form-control", "min": "1", "step": "0.01", "placeholder": "Monto pagado"}), + "method": forms.Select(attrs={"class": "form-select"}), + "reference": forms.TextInput(attrs={"class": "form-control", "placeholder": "No. operación / referencia"}), + "note": forms.Textarea(attrs={"class": "form-control", "rows": 4, "placeholder": "Detalles útiles: fecha, banco, servicio solicitado..."}), + "receipt": forms.ClearableFileInput(attrs={"class": "form-control"}), + } + labels = { + "product": "Producto o servicio", + "customer_name": "Nombre", + "customer_phone": "Teléfono / WhatsApp", + "customer_email": "Correo (opcional)", + "amount": "Monto pagado", + "method": "Método de pago", + "reference": "Referencia de pago", + "note": "Nota para el equipo", + "receipt": "Comprobante (opcional)", + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["product"].queryset = self.fields["product"].queryset.filter(is_active=True).select_related("category") + self.fields["product"].empty_label = "Selecciona un servicio" + + def clean_customer_phone(self): + phone = self.cleaned_data["customer_phone"].strip() + digits = [char for char in phone if char.isdigit()] + if len(digits) < 8: + raise forms.ValidationError("Escribe un teléfono válido para poder contactarte.") + return phone + + def clean_reference(self): + reference = self.cleaned_data["reference"].strip() + if len(reference) < 4: + raise forms.ValidationError("La referencia debe tener al menos 4 caracteres.") + return reference diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..690f90f --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,84 @@ +# Generated by Django 5.2.7 on 2026-06-30 00:15 + +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=160)), + ('slug', models.SlugField(blank=True, max_length=180, unique=True)), + ('short_description', models.CharField(max_length=240)), + ('description', models.TextField()), + ('price_from', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)])), + ('delivery_time', models.CharField(default='A convenir', max_length=80)), + ('is_featured', models.BooleanField(default=False)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'producto/servicio', + 'verbose_name_plural': 'productos y servicios', + 'ordering': ['category__sort_order', 'name'], + }, + ), + migrations.CreateModel( + name='ProductCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=120, unique=True)), + ('slug', models.SlugField(blank=True, max_length=140, unique=True)), + ('description', models.TextField(blank=True)), + ('accent', models.CharField(default='#00D1FF', max_length=20)), + ('icon', models.CharField(default='bi-cpu', max_length=40)), + ('is_active', models.BooleanField(default=True)), + ('sort_order', models.PositiveIntegerField(default=0)), + ], + options={ + 'verbose_name': 'categoría de producto', + 'verbose_name_plural': 'categorías de productos', + 'ordering': ['sort_order', 'name'], + }, + ), + migrations.CreateModel( + name='PaymentConfirmation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('customer_name', models.CharField(max_length=140)), + ('customer_phone', models.CharField(max_length=40)), + ('customer_email', models.EmailField(blank=True, max_length=254)), + ('amount', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(1)])), + ('method', models.CharField(choices=[('transfermovil', 'Transfermóvil'), ('enzona', 'Enzona'), ('qr', 'Código QR'), ('transferencia', 'Transferencia bancaria')], max_length=30)), + ('reference', models.CharField(max_length=120)), + ('note', models.TextField(blank=True)), + ('receipt', models.FileField(blank=True, upload_to='payment_receipts/')), + ('status', models.CharField(choices=[('pendiente', 'Pendiente'), ('aprobado', 'Aprobado'), ('rechazado', 'Rechazado')], default='pendiente', max_length=20)), + ('admin_note', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('reviewed_at', models.DateTimeField(blank=True, null=True)), + ('product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='payment_confirmations', to='core.product')), + ], + options={ + 'verbose_name': 'confirmación de pago', + 'verbose_name_plural': 'confirmaciones de pago', + 'ordering': ['-created_at'], + }, + ), + migrations.AddField( + model_name='product', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='core.productcategory'), + ), + ] diff --git a/core/migrations/0002_seed_tech_store.py b/core/migrations/0002_seed_tech_store.py new file mode 100644 index 0000000..5c17691 --- /dev/null +++ b/core/migrations/0002_seed_tech_store.py @@ -0,0 +1,65 @@ +# Generated seed data for the initial technology store MVP. +from django.db import migrations + + +def seed_catalog(apps, schema_editor): + ProductCategory = apps.get_model("core", "ProductCategory") + Product = apps.get_model("core", "Product") + + categories = [ + ("Software Empresarial", "software-empresarial", "Sistemas de gestión, inventario, ventas y operaciones para negocios.", "bi-window-stack", 1), + ("Hardware & Equipos", "hardware-equipos", "Equipos, componentes y kits tecnológicos para oficinas y proyectos.", "bi-pc-display", 2), + ("Desarrollo Web", "desarrollo-web", "Páginas web, tiendas virtuales y landings profesionales para vender online.", "bi-code-slash", 3), + ("Apps Móviles", "apps-moviles", "Aplicaciones móviles a medida para clientes, equipos y procesos internos.", "bi-phone", 4), + ("Soporte Técnico", "soporte-tecnico", "Mantenimiento, optimización, asistencia remota y soporte preventivo.", "bi-tools", 5), + ] + + created_categories = {} + for name, slug, description, icon, order in categories: + category, _ = ProductCategory.objects.update_or_create( + slug=slug, + defaults={"name": name, "description": description, "icon": icon, "sort_order": order, "is_active": True}, + ) + created_categories[slug] = category + + products = [ + ("software-empresarial", "Software Empresarial a Medida", "Gestiona ventas, clientes, inventario y reportes desde un panel propio.", "Diseñamos un sistema empresarial ajustado a tu operación: roles, panel administrativo, reportes, base de datos segura y módulos escalables para crecer.", 250.00, "Desde 15 días", True), + ("hardware-equipos", "Hardware & Equipos", "Asesoría y venta de equipos para oficinas, tiendas y proyectos digitales.", "Te ayudamos a seleccionar equipos, periféricos y componentes adecuados para tu negocio, con orientación técnica y soporte de instalación.", 80.00, "Según disponibilidad", True), + ("desarrollo-web", "Tienda Virtual Profesional", "Catálogo, carrito, pagos, confirmaciones y panel admin para vender online.", "Creamos tu tienda virtual con diseño moderno, catálogo administrable, integración de contacto y flujo de confirmación de pagos por QR, Transfermóvil o Enzona.", 180.00, "Desde 7 días", True), + ("desarrollo-web", "Página Web Corporativa", "Sitio moderno para presentar servicios, captar leads y posicionar tu marca.", "Landing o web multi-sección con diseño responsive, SEO base, formularios de contacto y llamadas a la acción para convertir visitas en clientes.", 120.00, "Desde 5 días", True), + ("apps-moviles", "Aplicación Móvil para Clientes", "App Android/iOS para reservas, pedidos, notificaciones o gestión interna.", "Construimos apps móviles conectadas a backend, con experiencia de usuario clara, flujos de autenticación y panel para administrar contenido.", 320.00, "Desde 20 días", True), + ("soporte-tecnico", "Soporte Técnico y Mantenimiento", "Diagnóstico, instalación, optimización y asistencia remota para equipos y sistemas.", "Servicio técnico para mantener tu operación estable: revisiones, instalación de software, respaldo de información y acompañamiento continuo.", 25.00, "24-48 horas", True), + ] + + for category_slug, name, short, description, price, delivery, featured in products: + Product.objects.update_or_create( + slug=name.lower().replace(" & ", "-").replace(" ", "-").replace("á", "a").replace("é", "e").replace("í", "i").replace("ó", "o").replace("ú", "u"), + defaults={ + "category": created_categories[category_slug], + "name": name, + "short_description": short, + "description": description, + "price_from": price, + "delivery_time": delivery, + "is_featured": featured, + "is_active": True, + }, + ) + + +def unseed_catalog(apps, schema_editor): + ProductCategory = apps.get_model("core", "ProductCategory") + ProductCategory.objects.filter(slug__in=[ + "software-empresarial", "hardware-equipos", "desarrollo-web", "apps-moviles", "soporte-tecnico" + ]).delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0001_initial"), + ] + + operations = [ + migrations.RunPython(seed_catalog, unseed_catalog), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..091456d421b80e33401242b7791f11e9bccf0198 GIT binary patch literal 5291 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 u{ - + - {% block title %}Knowledge Base{% endblock %} + + {% block title %}{{ page_title|default:project_name }} · App Tecno Store{% endblock %} + {% if project_description %} - {% endif %} @@ -13,13 +15,62 @@ {% endif %} - {% load static %} + + + + + {% block head %}{% endblock %} + + + + + {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + {% block content %}{% endblock %} + + + + + +
+
+
App Tecno Store
Tiendas virtuales, páginas web y apps móviles para crecer.
+ +
+
+ + diff --git a/core/templates/core/catalog.html b/core/templates/core/catalog.html new file mode 100644 index 0000000..55e56ba --- /dev/null +++ b/core/templates/core/catalog.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %}{{ page_title }} · App Tecno Store{% endblock %} + +{% block content %} +
+
+
+ Catálogo +

{% if selected_category %}{{ selected_category.name }}{% else %}Productos y servicios tecnológicos{% endif %}

+

Filtra por categoría, revisa detalles y avanza con contacto o confirmación de pago.

+
+ +
+ Todo + {% for category in categories %} + {{ category.name }} + {% endfor %} +
+ +
+ {% for product in products %} + {% include "core/partials/product_card.html" with product=product %} + {% empty %} +
+
No hay productos en esta categoría todavía. Entra al admin para activar nuevos servicios.
+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/core/templates/core/confirm_payment.html b/core/templates/core/confirm_payment.html new file mode 100644 index 0000000..7d86036 --- /dev/null +++ b/core/templates/core/confirm_payment.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block title %}Confirmar pago · App Tecno Store{% endblock %} + +{% block content %} +
+
+
+
+
+ Confirmación manual +

Reporta tu pago

+

Completa los datos de la operación. El administrador revisará la referencia y actualizará el estado desde el panel.

+
+
+
+

Métodos disponibles

+
QR de pago
+
TransfermóvilEnzonaTransferencia
+
+
+
+
+ {% csrf_token %} + {% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %} +
+ {% for field in form %} +
+ + {{ field }} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% for error in field.errors %}
{{ error }}
{% endfor %} +
+ {% endfor %} +
+ +

Tu pago queda en estado pendiente hasta revisión del administrador.

+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..ef7ff54 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,108 @@ {% extends "base.html" %} +{% load static %} -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% block title %}{{ page_title }} · App Tecno Store{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+
+
+
+
+
+ Tienda virtual tecnológica +

Compra, cotiza y confirma pagos de soluciones digitales en minutos.

+

Creamos tiendas virtuales, páginas web, software empresarial, apps móviles y soporte técnico con pagos por QR, Transfermóvil, Enzona o transferencia.

+ +
+
5Categorías activas
+
24/7Solicitudes abiertas
+
{{ pending_payments }}Pagos pendientes admin
+
+
+
+
+
+

Zona de pago

+

Escanea el QR o paga por las pasarelas disponibles. Luego reporta tu referencia para aprobación manual.

+
+
+ + QR de pago +
+
+
+ TransfermóvilEnzonaTransferencia +
+ Ya pagué, confirmar +
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

+
+ +
+
+
+
+ Servicios activos +

Catálogo listo para vender tecnología

+

Elige una categoría, abre el detalle y continúa por WhatsApp o confirmando tu pago.

+
+
+ {% for category in categories %} + + {% empty %} +
Aún no hay categorías. Puedes crearlas desde el panel admin.
+ {% endfor %} +
+
+
+ +
+
+
+
+ Primeros productos +

Soluciones destacadas

+
+ Explorar todo +
+
+ {% for product in featured_products %} + {% include "core/partials/product_card.html" with product=product %} + {% empty %} +
No hay productos destacados todavía.
+ {% endfor %} +
+
+
+ +
+
+
+
+ Flujo de pago manual +

Reporta tu operación y el admin la aprueba en el panel.

+

Diseñado para operar hoy mismo: el cliente paga por QR, Transfermóvil o Enzona, sube su referencia y el administrador revisa el estado desde Django Admin.

+
+ +
+
+
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/templates/core/partials/product_card.html b/core/templates/core/partials/product_card.html new file mode 100644 index 0000000..acd9848 --- /dev/null +++ b/core/templates/core/partials/product_card.html @@ -0,0 +1,18 @@ +
+
+
+ {{ product.category.name }} + +
+

{{ product.name }}

+

{{ product.short_description }}

+
+ Desde ${{ product.price_from }} + {{ product.delivery_time }} +
+ +
+
diff --git a/core/templates/core/payment_success.html b/core/templates/core/payment_success.html new file mode 100644 index 0000000..ba2c117 --- /dev/null +++ b/core/templates/core/payment_success.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block title %}Pago recibido · App Tecno Store{% endblock %} + +{% block content %} +
+
+
+
+ Solicitud recibida +

Tu pago está pendiente de aprobación

+

Registramos la confirmación de {{ confirmation.customer_name }}. El admin revisará la referencia {{ confirmation.reference }} y actualizará el estado.

+
+
Método{{ confirmation.get_method_display }}
+
Monto${{ confirmation.amount }}
+
Estado{{ confirmation.get_status_display }}
+
Servicio{{ confirmation.product|default:'No especificado' }}
+
+ +
+
+
+{% endblock %} diff --git a/core/templates/core/product_detail.html b/core/templates/core/product_detail.html new file mode 100644 index 0000000..a943b2e --- /dev/null +++ b/core/templates/core/product_detail.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% block title %}{{ product.name }} · App Tecno Store{% endblock %} + +{% block content %} +
+
+
+
+ Volver al catálogo +
+ {{ product.category.name }} +

{{ product.name }}

+

{{ product.short_description }}

+
Desde ${{ product.price_from }}
+
{{ product.description|linebreaks }}
+ +
+
+ +
+ + {% if related_products %} +
+

También puede interesarte

+
+ {% for product in related_products %} + {% include "core/partials/product_card.html" with product=product %} + {% endfor %} +
+
+ {% endif %} +
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..4343ad2 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,11 @@ from django.urls import path -from .views import home +from .views import catalog, confirm_payment, home, payment_success, product_detail urlpatterns = [ path("", home, name="home"), + path("catalogo/", catalog, name="catalog"), + path("servicio//", product_detail, name="product_detail"), + path("confirmar-pago/", confirm_payment, name="confirm_payment"), + path("confirmar-pago//recibido/", payment_success, name="payment_success"), ] diff --git a/core/views.py b/core/views.py index c9aed12..32399a6 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,120 @@ -import os -import platform +from django.contrib import messages +from django.db.models import Count, Q +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone +from .forms import PaymentConfirmationForm +from .models import PaymentConfirmation, Product, ProductCategory + +WHATSAPP_NUMBER = "5359177041" + + +def _base_context(**extra): + context = { + "project_name": "App Tecno Store", + "project_description": "Tienda virtual de servicios tecnológicos con pagos por QR, Transfermóvil y Enzona.", + "whatsapp_number": WHATSAPP_NUMBER, + } + context.update(extra) + return context def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + categories = ProductCategory.objects.filter(is_active=True).annotate( + active_products=Count("products", filter=Q(products__is_active=True)) + ) + featured_products = Product.objects.filter(is_active=True, is_featured=True).select_related("category")[:6] + recent_confirmations = PaymentConfirmation.objects.filter(status=PaymentConfirmation.STATUS_PENDING).count() + payment_form = PaymentConfirmationForm() - context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), - } - return render(request, "core/index.html", context) + return render( + request, + "core/index.html", + _base_context( + categories=categories, + featured_products=featured_products, + payment_form=payment_form, + pending_payments=recent_confirmations, + page_title="Tienda virtual de soluciones tecnológicas", + meta_description="Compra o cotiza software empresarial, hardware, desarrollo web, apps móviles y soporte técnico con pagos por QR, Transfermóvil o Enzona.", + ), + ) + + +def catalog(request): + category_slug = request.GET.get("categoria") + categories = ProductCategory.objects.filter(is_active=True) + products = Product.objects.filter(is_active=True).select_related("category") + selected_category = None + if category_slug: + selected_category = get_object_or_404(ProductCategory, slug=category_slug, is_active=True) + products = products.filter(category=selected_category) + + return render( + request, + "core/catalog.html", + _base_context( + categories=categories, + products=products, + selected_category=selected_category, + page_title="Catálogo tecnológico", + meta_description="Explora productos y servicios tecnológicos listos para cotizar y pagar en línea.", + ), + ) + + +def product_detail(request, slug): + product = get_object_or_404(Product.objects.select_related("category"), slug=slug, is_active=True) + related_products = Product.objects.filter(is_active=True, category=product.category).exclude(pk=product.pk)[:3] + payment_form = PaymentConfirmationForm(initial={"product": product, "amount": product.price_from}) + return render( + request, + "core/product_detail.html", + _base_context( + product=product, + related_products=related_products, + payment_form=payment_form, + page_title=product.name, + meta_description=product.short_description, + ), + ) + + +def confirm_payment(request): + initial = {} + product_slug = request.GET.get("producto") + if product_slug: + product = get_object_or_404(Product, slug=product_slug, is_active=True) + initial = {"product": product, "amount": product.price_from} + + if request.method == "POST": + form = PaymentConfirmationForm(request.POST, request.FILES) + if form.is_valid(): + confirmation = form.save() + messages.success(request, "Tu confirmación fue enviada. Un administrador revisará el pago en el panel.") + return redirect("payment_success", pk=confirmation.pk) + else: + form = PaymentConfirmationForm(initial=initial) + + return render( + request, + "core/confirm_payment.html", + _base_context( + form=form, + page_title="Confirmar pago", + meta_description="Reporta tu pago por QR, Transfermóvil, Enzona o transferencia para revisión manual del administrador.", + ), + ) + + +def payment_success(request, pk): + confirmation = get_object_or_404(PaymentConfirmation.objects.select_related("product"), pk=pk) + return render( + request, + "core/payment_success.html", + _base_context( + confirmation=confirmation, + page_title="Pago recibido para revisión", + meta_description="Confirmación de pago recibida y pendiente de aprobación manual.", + ), + ) diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..2b8e2e3 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,214 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +:root { + --navy: #061724; + --navy-2: #0b2436; + --primary: #00d1ff; + --secondary: #00f2a9; + --accent: #ffb703; + --paper: #f7fbff; + --muted: #6b7a88; + --ink: #10212f; + --line: rgba(10, 36, 54, 0.12); + --glass: rgba(255, 255, 255, 0.12); + --shadow: 0 24px 70px rgba(4, 18, 30, 0.18); +} + +* { box-sizing: border-box; } + +body { + margin: 0; + font-family: 'Manrope', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + color: var(--ink); + background: + radial-gradient(circle at 10% 10%, rgba(0, 209, 255, 0.12), transparent 32rem), + radial-gradient(circle at 90% 0%, rgba(0, 242, 169, 0.10), transparent 30rem), + var(--paper); + overflow-x: hidden; +} + +h1, h2, h3, .brand-mark { font-family: 'Space Grotesk', 'Manrope', sans-serif; letter-spacing: -0.04em; } +a { text-decoration: none; } + +.app-nav { + background: rgba(6, 23, 36, 0.88); + backdrop-filter: blur(18px); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} +.brand-mark { font-weight: 800; display: inline-flex; align-items: center; gap: .55rem; } +.brand-logo { + display: inline-grid; + place-items: center; + width: 2.75rem; + height: 2.75rem; + border-radius: 1rem; + color: var(--navy); + background: linear-gradient(135deg, var(--primary), var(--secondary)); + box-shadow: 0 10px 30px rgba(0, 209, 255, .3); + font-size: .86rem; +} +.navbar .nav-link { color: rgba(255,255,255,.76); font-weight: 700; } +.navbar .nav-link:hover, .navbar .nav-link:focus { color: #fff; } + +.btn { border-radius: 999px; font-weight: 800; padding: .78rem 1.2rem; } +.btn-accent { + color: #03131d; + border: 0; + background: linear-gradient(135deg, var(--secondary), var(--primary)); + box-shadow: 0 14px 36px rgba(0, 209, 255, .28); +} +.btn-accent:hover, .btn-accent:focus { color: #03131d; transform: translateY(-1px); box-shadow: 0 18px 44px rgba(0, 209, 255, .35); } +.btn-glass { color: #fff; border: 1px solid rgba(255,255,255,.28); background: rgba(255,255,255,.08); backdrop-filter: blur(12px); } +.btn-glass:hover, .btn-glass:focus { color: #fff; border-color: rgba(255,255,255,.55); background: rgba(255,255,255,.16); } +.btn-dark-soft { color: var(--navy); background: #e8f4fb; border: 1px solid rgba(6,23,36,.08); } +.btn-dark-soft:hover, .btn-dark-soft:focus { color: #fff; background: var(--navy); } + +.tech-loader { + position: fixed; + inset: 0; + z-index: 2000; + display: grid; + place-items: center; + gap: 1rem; + background: radial-gradient(circle, #0b3149, var(--navy)); + color: #dff8ff; + animation: loaderExit .9s ease 1.35s forwards; + pointer-events: none; +} +.loader-orb { + width: 5rem; + height: 5rem; + border-radius: 50%; + border: 2px solid rgba(255,255,255,.15); + background: conic-gradient(from 0deg, var(--primary), var(--secondary), transparent, var(--primary)); + mask: radial-gradient(circle, transparent 48%, #000 50%); + animation: spin 1s linear infinite; +} +.tech-loader span { margin-top: 7rem; position: absolute; font-weight: 800; letter-spacing: .02em; } +@keyframes spin { to { transform: rotate(360deg); } } +@keyframes loaderExit { to { opacity: 0; visibility: hidden; } } + +.hero-section { + position: relative; + min-height: 86vh; + color: #fff; + background: + linear-gradient(135deg, rgba(6,23,36,.98), rgba(8,44,67,.95)), + radial-gradient(circle at 78% 18%, rgba(0, 209, 255, .30), transparent 28rem); + overflow: hidden; +} +.hero-grid { + position: absolute; + inset: 0; + opacity: .23; + background-image: linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px); + background-size: 46px 46px; + animation: panGrid 18s linear infinite; +} +@keyframes panGrid { to { background-position: 92px 92px; } } +.shape { position: absolute; border-radius: 32px; filter: blur(.2px); opacity: .8; animation: floatShape 7s ease-in-out infinite; } +.shape-one { width: 9rem; height: 9rem; right: 8%; top: 14%; background: linear-gradient(135deg, var(--secondary), transparent); transform: rotate(18deg); } +.shape-two { width: 6rem; height: 6rem; left: 7%; bottom: 12%; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--accent)); animation-delay: -2s; } +@keyframes floatShape { 50% { transform: translateY(-18px) rotate(28deg); } } + +.eyebrow { display: inline-flex; align-items: center; gap: .45rem; text-transform: uppercase; font-size: .78rem; letter-spacing: .13em; font-weight: 900; color: var(--secondary); } +.eyebrow.dark { color: #0089a9; } +.hero-copy { max-width: 44rem; color: rgba(255,255,255,.78); font-size: clamp(1.08rem, 1vw + .8rem, 1.32rem); line-height: 1.8; } +.hero-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .8rem; max-width: 40rem; } +.hero-stats div { padding: 1rem; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.07); border-radius: 1.35rem; } +.hero-stats strong { display: block; font-size: 1.6rem; color: #fff; } +.hero-stats span { color: rgba(255,255,255,.68); font-size: .86rem; } + +.payment-console, .detail-panel, .form-panel, .success-panel, .cta-panel, .product-card, .category-card, .empty-state, .filter-bar { + border: 1px solid var(--line); + box-shadow: var(--shadow); +} +.payment-console { + padding: 1.35rem; + border-radius: 2rem; + color: #fff; + background: linear-gradient(145deg, rgba(255,255,255,.13), rgba(255,255,255,.05)); + backdrop-filter: blur(18px); +} +.console-top { display: flex; gap: .45rem; margin-bottom: 1.2rem; } +.console-top span { width: .72rem; height: .72rem; border-radius: 999px; background: var(--secondary); } +.console-top span:nth-child(2) { background: var(--primary); } +.console-top span:nth-child(3) { background: var(--accent); } +.qr-card { border-radius: 1.35rem; padding: .8rem; background: rgba(255,255,255,.10); border: 1px dashed rgba(255,255,255,.30); } +.qr-placeholder { + aspect-ratio: 1; + min-height: 210px; + display: grid; + place-items: center; + text-align: center; + border-radius: 1rem; + color: #061724; + background: + linear-gradient(45deg, #fff 25%, #d9f8ff 25% 50%, #fff 50% 75%, #d9f8ff 75%) 0 0/28px 28px; +} +.qr-placeholder i { font-size: 4rem; display: block; } +.qr-placeholder span { display: block; font-weight: 900; } +.payment-methods { display: flex; flex-wrap: wrap; gap: .55rem; margin-top: 1rem; } +.payment-methods span { padding: .5rem .75rem; border-radius: 999px; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.16); font-weight: 800; font-size: .82rem; } + +.section-pad { padding: 5.5rem 0; } +.section-heading { max-width: 46rem; } +.section-heading h2, .cta-panel h2 { font-size: clamp(2rem, 3vw, 3.2rem); } +.section-heading p { color: var(--muted); font-size: 1.08rem; } +.category-card, .product-card { display: block; height: 100%; border-radius: 1.5rem; padding: 1.35rem; background: rgba(255,255,255,.82); color: var(--ink); transition: .22s ease; } +.category-card:hover, .product-card:hover { transform: translateY(-6px); border-color: rgba(0,209,255,.45); } +.category-icon { width: 3.3rem; height: 3.3rem; border-radius: 1rem; display: grid; place-items: center; color: #061724; background: linear-gradient(135deg, var(--primary), var(--secondary)); margin-bottom: 1rem; font-size: 1.45rem; } +.category-card h3, .product-card h3 { font-size: 1.28rem; margin-bottom: .55rem; } +.category-card p, .product-card p { color: var(--muted); } +.category-card small { font-weight: 900; color: #0089a9; } +.product-strip { background: linear-gradient(180deg, rgba(232,244,251,.55), rgba(247,251,255,0)); } +.product-card-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; } +.product-card-top i { font-size: 1.75rem; color: #00a6c8; } +.pill { display: inline-flex; padding: .45rem .75rem; border-radius: 999px; font-size: .78rem; font-weight: 900; color: #006e87; background: #dcf8ff; } +.product-meta { display: flex; justify-content: space-between; gap: 1rem; color: var(--muted); border-top: 1px solid var(--line); padding-top: 1rem; font-size: .92rem; } +.product-meta strong { color: var(--navy); } +.cta-panel { border-radius: 2rem; padding: clamp(1.5rem, 4vw, 3rem); color: #fff; background: linear-gradient(135deg, var(--navy), #0a3a57); overflow: hidden; position: relative; } +.cta-panel p { color: rgba(255,255,255,.76); } + +.page-shell { padding: 4.5rem 0 6rem; } +.page-hero { text-align: center; max-width: 760px; margin: 0 auto 2rem; } +.page-hero.compact { padding: 2rem; border-radius: 2rem; color: #fff; background: linear-gradient(135deg, var(--navy), #0d415f); box-shadow: var(--shadow); } +.page-hero h1 { font-size: clamp(2.2rem, 4vw, 4rem); } +.page-hero p { color: rgba(255,255,255,.75); font-size: 1.08rem; } +.filter-bar { display: flex; flex-wrap: wrap; gap: .65rem; padding: .75rem; border-radius: 999px; background: #fff; } +.filter-chip { color: var(--muted); border-radius: 999px; padding: .72rem 1rem; font-weight: 900; } +.filter-chip.active, .filter-chip:hover { color: var(--navy); background: #dcf8ff; } +.back-link { color: #007f9c; font-weight: 900; } +.detail-panel, .form-panel, .success-panel { border-radius: 2rem; padding: clamp(1.4rem, 4vw, 2.4rem); background: rgba(255,255,255,.88); } +.detail-panel h1 { font-size: clamp(2.2rem, 4vw, 4rem); margin-top: 1rem; } +.detail-price { font-size: 1.25rem; margin: 1.4rem 0; color: var(--muted); } +.detail-price strong { color: var(--navy); font-size: 2rem; } +.detail-body { color: #415363; line-height: 1.85; } +.detail-actions { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 2rem; } +.detail-payment { top: 6rem; background: linear-gradient(145deg, #0a2d43, #061724); } +.form-panel .form-control, .form-panel .form-select { border-radius: 1rem; padding: .9rem 1rem; border: 1px solid rgba(6,23,36,.12); } +.form-panel .form-control:focus, .form-panel .form-select:focus { border-color: var(--primary); box-shadow: 0 0 0 .25rem rgba(0,209,255,.14); } +.form-label { font-weight: 900; } +.form-note { color: var(--muted); } +.success-panel { max-width: 850px; text-align: center; } +.success-icon { width: 5rem; height: 5rem; margin: 0 auto 1rem; display: grid; place-items: center; border-radius: 50%; color: var(--navy); background: linear-gradient(135deg, var(--secondary), var(--primary)); font-size: 2.4rem; } +.summary-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .75rem; margin-top: 1.5rem; } +.summary-grid div { padding: 1rem; border-radius: 1rem; background: #edf8fc; } +.summary-grid span { display: block; color: var(--muted); font-size: .82rem; font-weight: 800; } +.summary-grid strong { display: block; color: var(--navy); } +.empty-state { padding: 2rem; border-radius: 1.5rem; background: #fff; color: var(--muted); text-align: center; } + +.whatsapp-float { position: fixed; right: 1.2rem; bottom: 1.2rem; z-index: 1000; display: grid; place-items: center; width: 3.8rem; height: 3.8rem; border-radius: 50%; color: #061724; background: linear-gradient(135deg, var(--secondary), var(--primary)); box-shadow: 0 18px 42px rgba(0,209,255,.38); font-size: 1.75rem; } +.whatsapp-float:hover { color: #061724; transform: translateY(-2px); } +.site-footer { padding: 2rem 0; color: rgba(255,255,255,.72); background: var(--navy); } +.site-footer strong { color: #fff; } +.footer-payments { color: var(--secondary); font-weight: 900; } + +@media (max-width: 991.98px) { + .hero-stats, .summary-grid { grid-template-columns: 1fr; } + .filter-bar { border-radius: 1.25rem; } + .page-shell { padding-top: 2.5rem; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; } + .tech-loader { display: none; } } diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 108056f..2b8e2e3 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -1,21 +1,214 @@ - :root { - --bg-color-start: #6a11cb; - --bg-color-end: #2575fc; - --text-color: #ffffff; - --card-bg-color: rgba(255, 255, 255, 0.01); - --card-border-color: rgba(255, 255, 255, 0.1); + --navy: #061724; + --navy-2: #0b2436; + --primary: #00d1ff; + --secondary: #00f2a9; + --accent: #ffb703; + --paper: #f7fbff; + --muted: #6b7a88; + --ink: #10212f; + --line: rgba(10, 36, 54, 0.12); + --glass: rgba(255, 255, 255, 0.12); + --shadow: 0 24px 70px rgba(4, 18, 30, 0.18); } + +* { box-sizing: border-box; } + body { margin: 0; - font-family: 'Inter', sans-serif; - background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); - color: var(--text-color); - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - text-align: center; - overflow: hidden; - position: relative; + font-family: 'Manrope', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + color: var(--ink); + background: + radial-gradient(circle at 10% 10%, rgba(0, 209, 255, 0.12), transparent 32rem), + radial-gradient(circle at 90% 0%, rgba(0, 242, 169, 0.10), transparent 30rem), + var(--paper); + overflow-x: hidden; +} + +h1, h2, h3, .brand-mark { font-family: 'Space Grotesk', 'Manrope', sans-serif; letter-spacing: -0.04em; } +a { text-decoration: none; } + +.app-nav { + background: rgba(6, 23, 36, 0.88); + backdrop-filter: blur(18px); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} +.brand-mark { font-weight: 800; display: inline-flex; align-items: center; gap: .55rem; } +.brand-logo { + display: inline-grid; + place-items: center; + width: 2.75rem; + height: 2.75rem; + border-radius: 1rem; + color: var(--navy); + background: linear-gradient(135deg, var(--primary), var(--secondary)); + box-shadow: 0 10px 30px rgba(0, 209, 255, .3); + font-size: .86rem; +} +.navbar .nav-link { color: rgba(255,255,255,.76); font-weight: 700; } +.navbar .nav-link:hover, .navbar .nav-link:focus { color: #fff; } + +.btn { border-radius: 999px; font-weight: 800; padding: .78rem 1.2rem; } +.btn-accent { + color: #03131d; + border: 0; + background: linear-gradient(135deg, var(--secondary), var(--primary)); + box-shadow: 0 14px 36px rgba(0, 209, 255, .28); +} +.btn-accent:hover, .btn-accent:focus { color: #03131d; transform: translateY(-1px); box-shadow: 0 18px 44px rgba(0, 209, 255, .35); } +.btn-glass { color: #fff; border: 1px solid rgba(255,255,255,.28); background: rgba(255,255,255,.08); backdrop-filter: blur(12px); } +.btn-glass:hover, .btn-glass:focus { color: #fff; border-color: rgba(255,255,255,.55); background: rgba(255,255,255,.16); } +.btn-dark-soft { color: var(--navy); background: #e8f4fb; border: 1px solid rgba(6,23,36,.08); } +.btn-dark-soft:hover, .btn-dark-soft:focus { color: #fff; background: var(--navy); } + +.tech-loader { + position: fixed; + inset: 0; + z-index: 2000; + display: grid; + place-items: center; + gap: 1rem; + background: radial-gradient(circle, #0b3149, var(--navy)); + color: #dff8ff; + animation: loaderExit .9s ease 1.35s forwards; + pointer-events: none; +} +.loader-orb { + width: 5rem; + height: 5rem; + border-radius: 50%; + border: 2px solid rgba(255,255,255,.15); + background: conic-gradient(from 0deg, var(--primary), var(--secondary), transparent, var(--primary)); + mask: radial-gradient(circle, transparent 48%, #000 50%); + animation: spin 1s linear infinite; +} +.tech-loader span { margin-top: 7rem; position: absolute; font-weight: 800; letter-spacing: .02em; } +@keyframes spin { to { transform: rotate(360deg); } } +@keyframes loaderExit { to { opacity: 0; visibility: hidden; } } + +.hero-section { + position: relative; + min-height: 86vh; + color: #fff; + background: + linear-gradient(135deg, rgba(6,23,36,.98), rgba(8,44,67,.95)), + radial-gradient(circle at 78% 18%, rgba(0, 209, 255, .30), transparent 28rem); + overflow: hidden; +} +.hero-grid { + position: absolute; + inset: 0; + opacity: .23; + background-image: linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px); + background-size: 46px 46px; + animation: panGrid 18s linear infinite; +} +@keyframes panGrid { to { background-position: 92px 92px; } } +.shape { position: absolute; border-radius: 32px; filter: blur(.2px); opacity: .8; animation: floatShape 7s ease-in-out infinite; } +.shape-one { width: 9rem; height: 9rem; right: 8%; top: 14%; background: linear-gradient(135deg, var(--secondary), transparent); transform: rotate(18deg); } +.shape-two { width: 6rem; height: 6rem; left: 7%; bottom: 12%; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--accent)); animation-delay: -2s; } +@keyframes floatShape { 50% { transform: translateY(-18px) rotate(28deg); } } + +.eyebrow { display: inline-flex; align-items: center; gap: .45rem; text-transform: uppercase; font-size: .78rem; letter-spacing: .13em; font-weight: 900; color: var(--secondary); } +.eyebrow.dark { color: #0089a9; } +.hero-copy { max-width: 44rem; color: rgba(255,255,255,.78); font-size: clamp(1.08rem, 1vw + .8rem, 1.32rem); line-height: 1.8; } +.hero-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .8rem; max-width: 40rem; } +.hero-stats div { padding: 1rem; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.07); border-radius: 1.35rem; } +.hero-stats strong { display: block; font-size: 1.6rem; color: #fff; } +.hero-stats span { color: rgba(255,255,255,.68); font-size: .86rem; } + +.payment-console, .detail-panel, .form-panel, .success-panel, .cta-panel, .product-card, .category-card, .empty-state, .filter-bar { + border: 1px solid var(--line); + box-shadow: var(--shadow); +} +.payment-console { + padding: 1.35rem; + border-radius: 2rem; + color: #fff; + background: linear-gradient(145deg, rgba(255,255,255,.13), rgba(255,255,255,.05)); + backdrop-filter: blur(18px); +} +.console-top { display: flex; gap: .45rem; margin-bottom: 1.2rem; } +.console-top span { width: .72rem; height: .72rem; border-radius: 999px; background: var(--secondary); } +.console-top span:nth-child(2) { background: var(--primary); } +.console-top span:nth-child(3) { background: var(--accent); } +.qr-card { border-radius: 1.35rem; padding: .8rem; background: rgba(255,255,255,.10); border: 1px dashed rgba(255,255,255,.30); } +.qr-placeholder { + aspect-ratio: 1; + min-height: 210px; + display: grid; + place-items: center; + text-align: center; + border-radius: 1rem; + color: #061724; + background: + linear-gradient(45deg, #fff 25%, #d9f8ff 25% 50%, #fff 50% 75%, #d9f8ff 75%) 0 0/28px 28px; +} +.qr-placeholder i { font-size: 4rem; display: block; } +.qr-placeholder span { display: block; font-weight: 900; } +.payment-methods { display: flex; flex-wrap: wrap; gap: .55rem; margin-top: 1rem; } +.payment-methods span { padding: .5rem .75rem; border-radius: 999px; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.16); font-weight: 800; font-size: .82rem; } + +.section-pad { padding: 5.5rem 0; } +.section-heading { max-width: 46rem; } +.section-heading h2, .cta-panel h2 { font-size: clamp(2rem, 3vw, 3.2rem); } +.section-heading p { color: var(--muted); font-size: 1.08rem; } +.category-card, .product-card { display: block; height: 100%; border-radius: 1.5rem; padding: 1.35rem; background: rgba(255,255,255,.82); color: var(--ink); transition: .22s ease; } +.category-card:hover, .product-card:hover { transform: translateY(-6px); border-color: rgba(0,209,255,.45); } +.category-icon { width: 3.3rem; height: 3.3rem; border-radius: 1rem; display: grid; place-items: center; color: #061724; background: linear-gradient(135deg, var(--primary), var(--secondary)); margin-bottom: 1rem; font-size: 1.45rem; } +.category-card h3, .product-card h3 { font-size: 1.28rem; margin-bottom: .55rem; } +.category-card p, .product-card p { color: var(--muted); } +.category-card small { font-weight: 900; color: #0089a9; } +.product-strip { background: linear-gradient(180deg, rgba(232,244,251,.55), rgba(247,251,255,0)); } +.product-card-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; } +.product-card-top i { font-size: 1.75rem; color: #00a6c8; } +.pill { display: inline-flex; padding: .45rem .75rem; border-radius: 999px; font-size: .78rem; font-weight: 900; color: #006e87; background: #dcf8ff; } +.product-meta { display: flex; justify-content: space-between; gap: 1rem; color: var(--muted); border-top: 1px solid var(--line); padding-top: 1rem; font-size: .92rem; } +.product-meta strong { color: var(--navy); } +.cta-panel { border-radius: 2rem; padding: clamp(1.5rem, 4vw, 3rem); color: #fff; background: linear-gradient(135deg, var(--navy), #0a3a57); overflow: hidden; position: relative; } +.cta-panel p { color: rgba(255,255,255,.76); } + +.page-shell { padding: 4.5rem 0 6rem; } +.page-hero { text-align: center; max-width: 760px; margin: 0 auto 2rem; } +.page-hero.compact { padding: 2rem; border-radius: 2rem; color: #fff; background: linear-gradient(135deg, var(--navy), #0d415f); box-shadow: var(--shadow); } +.page-hero h1 { font-size: clamp(2.2rem, 4vw, 4rem); } +.page-hero p { color: rgba(255,255,255,.75); font-size: 1.08rem; } +.filter-bar { display: flex; flex-wrap: wrap; gap: .65rem; padding: .75rem; border-radius: 999px; background: #fff; } +.filter-chip { color: var(--muted); border-radius: 999px; padding: .72rem 1rem; font-weight: 900; } +.filter-chip.active, .filter-chip:hover { color: var(--navy); background: #dcf8ff; } +.back-link { color: #007f9c; font-weight: 900; } +.detail-panel, .form-panel, .success-panel { border-radius: 2rem; padding: clamp(1.4rem, 4vw, 2.4rem); background: rgba(255,255,255,.88); } +.detail-panel h1 { font-size: clamp(2.2rem, 4vw, 4rem); margin-top: 1rem; } +.detail-price { font-size: 1.25rem; margin: 1.4rem 0; color: var(--muted); } +.detail-price strong { color: var(--navy); font-size: 2rem; } +.detail-body { color: #415363; line-height: 1.85; } +.detail-actions { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 2rem; } +.detail-payment { top: 6rem; background: linear-gradient(145deg, #0a2d43, #061724); } +.form-panel .form-control, .form-panel .form-select { border-radius: 1rem; padding: .9rem 1rem; border: 1px solid rgba(6,23,36,.12); } +.form-panel .form-control:focus, .form-panel .form-select:focus { border-color: var(--primary); box-shadow: 0 0 0 .25rem rgba(0,209,255,.14); } +.form-label { font-weight: 900; } +.form-note { color: var(--muted); } +.success-panel { max-width: 850px; text-align: center; } +.success-icon { width: 5rem; height: 5rem; margin: 0 auto 1rem; display: grid; place-items: center; border-radius: 50%; color: var(--navy); background: linear-gradient(135deg, var(--secondary), var(--primary)); font-size: 2.4rem; } +.summary-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .75rem; margin-top: 1.5rem; } +.summary-grid div { padding: 1rem; border-radius: 1rem; background: #edf8fc; } +.summary-grid span { display: block; color: var(--muted); font-size: .82rem; font-weight: 800; } +.summary-grid strong { display: block; color: var(--navy); } +.empty-state { padding: 2rem; border-radius: 1.5rem; background: #fff; color: var(--muted); text-align: center; } + +.whatsapp-float { position: fixed; right: 1.2rem; bottom: 1.2rem; z-index: 1000; display: grid; place-items: center; width: 3.8rem; height: 3.8rem; border-radius: 50%; color: #061724; background: linear-gradient(135deg, var(--secondary), var(--primary)); box-shadow: 0 18px 42px rgba(0,209,255,.38); font-size: 1.75rem; } +.whatsapp-float:hover { color: #061724; transform: translateY(-2px); } +.site-footer { padding: 2rem 0; color: rgba(255,255,255,.72); background: var(--navy); } +.site-footer strong { color: #fff; } +.footer-payments { color: var(--secondary); font-weight: 900; } + +@media (max-width: 991.98px) { + .hero-stats, .summary-grid { grid-template-columns: 1fr; } + .filter-bar { border-radius: 1.25rem; } + .page-shell { padding-top: 2.5rem; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; } + .tech-loader { display: none; } }