From cbcdb823db217444008503d8af36be62dd08d0a6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 9 Apr 2026 10:03:57 +0200 Subject: [PATCH] feat: Add stock management features including stock reservations and updates to cart and checkout processes --- proyecto/__pycache__/__init__.cpython-314.pyc | Bin 243 -> 0 bytes proyecto/__pycache__/settings.cpython-314.pyc | Bin 11793 -> 0 bytes proyecto/__pycache__/urls.cpython-314.pyc | Bin 1551 -> 0 bytes proyecto/__pycache__/wsgi.cpython-314.pyc | Bin 654 -> 0 bytes tienda/__pycache__/__init__.cpython-314.pyc | Bin 167 -> 0 bytes tienda/__pycache__/admin.cpython-314.pyc | Bin 4731 -> 0 bytes tienda/__pycache__/apps.cpython-314.pyc | Bin 467 -> 0 bytes .../context_processors.cpython-314.pyc | Bin 1366 -> 0 bytes tienda/__pycache__/models.cpython-314.pyc | Bin 16229 -> 0 bytes tienda/__pycache__/urls.cpython-314.pyc | Bin 4842 -> 0 bytes tienda/__pycache__/vars.cpython-314.pyc | Bin 2214 -> 0 bytes tienda/__pycache__/views.cpython-314.pyc | Bin 70749 -> 0 bytes ...k_stockreservation_stockreservationitem.py | 45 +++++ .../__pycache__/0001_initial.cpython-314.pyc | Bin 12698 -> 0 bytes ...ioncode_code_mode_and_more.cpython-314.pyc | Bin 1069 -> 0 bytes .../__pycache__/__init__.cpython-314.pyc | Bin 178 -> 0 bytes tienda/templates/tienda/cart.html | 23 ++- tienda/templates/tienda/checkout.html | 28 ++- tienda/templates/tienda/crear_producto.html | 8 + tienda/templates/tienda/editar_producto.html | 8 + tienda/templates/tienda/mis_productos.html | 2 + tienda/templates/tienda/producto.html | 11 +- .../__pycache__/__init__.cpython-314.pyc | Bin 180 -> 0 bytes .../__pycache__/vat_filters.cpython-314.pyc | Bin 1496 -> 0 bytes tienda/views.py | 179 ++++++++++++++++-- 25 files changed, 279 insertions(+), 25 deletions(-) delete mode 100644 proyecto/__pycache__/__init__.cpython-314.pyc delete mode 100644 proyecto/__pycache__/settings.cpython-314.pyc delete mode 100644 proyecto/__pycache__/urls.cpython-314.pyc delete mode 100644 proyecto/__pycache__/wsgi.cpython-314.pyc delete mode 100644 tienda/__pycache__/__init__.cpython-314.pyc delete mode 100644 tienda/__pycache__/admin.cpython-314.pyc delete mode 100644 tienda/__pycache__/apps.cpython-314.pyc delete mode 100644 tienda/__pycache__/context_processors.cpython-314.pyc delete mode 100644 tienda/__pycache__/models.cpython-314.pyc delete mode 100644 tienda/__pycache__/urls.cpython-314.pyc delete mode 100644 tienda/__pycache__/vars.cpython-314.pyc delete mode 100644 tienda/__pycache__/views.cpython-314.pyc create mode 100644 tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py delete mode 100644 tienda/migrations/__pycache__/0001_initial.cpython-314.pyc delete mode 100644 tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc delete mode 100644 tienda/migrations/__pycache__/__init__.cpython-314.pyc delete mode 100644 tienda/templatetags/__pycache__/__init__.cpython-314.pyc delete mode 100644 tienda/templatetags/__pycache__/vat_filters.cpython-314.pyc diff --git a/proyecto/__pycache__/__init__.cpython-314.pyc b/proyecto/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index 8250ca705cea9cfd9618ad5aed07965880649c79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 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 diff --git a/proyecto/__pycache__/urls.cpython-314.pyc b/proyecto/__pycache__/urls.cpython-314.pyc deleted file mode 100644 index 658ce2169e69a56919e078170c49077c39fe407c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1551 zcmb7E-*4MQ96u))CvlRt8k2x(>aL8Dcnda6V-r#7w6SiEU~6P;JP|TCzBZm>pLKTu z+7~K>SRd)r_Q)&$0sltTp@OA~2mXOp6Yt!e?Ir>t!AZ71-|u~Ye7@iB`L5123?PR*es}&7^UEl%^z5uS))tmw^T$H%bEjo}xg^S&iqjOzh*eyF1UU90t zs^n7FaB94M5%jCez}5Tp-eR6)`gO~0zMGlgw*a^R`U`!y420#|vouP=#VmAB#0Uw+D2PP3Fby1!vBxlC74qer}G44&Gq2jF4s-W*%=(g`8FG4{W z6UsXXZBi1VaC_jzai&aZD*J1hy{teMFjJv$6~&HP&1O5RYeYiaMv)idkD3xUP2=OS z$Jo0YqA-6Z>c6ode1xeaVkyQs9Y@6aKWwDE<*|Vb(lYWhZS{Iu1)VJSUmiwpCO9Guw?Z*|J3g^Gr>tP;r~4X_#74 zVVHBlmIy@ejX-)`g_m7wKFsYGNl!7CzX|u_>(bSrSe{r z_Y?b0yiSUmrkxh%HTWk2+VU~DatzS#;PNqOo$8=e-8;AU`B7u(pt1DhXD?yNfI+3N1F~ zqgN=vKGxX0mTx__wS5J>#Ct1M$@1N6EV-h<4r&|LJTi6mRkX24>GJ`;j#}rAe>gAt zU7ti!3|T^kGACIsClF~_!hr$TaePhSO(qp6qDMgH6_j)OnggMVP?E~{ki%^bW8h>= zb2du}nnFwl1Da)=MN~5>1_RUYk&#Jd4ulD#n9dSJ3e%h^GGrWX5e49kw9eF~=fyHo zZhSyRA}ddkOg#|ZJ-6+RlY`6omGURKh%`*TTi+1aw%2;TZ?TdgsagUUxU_ z@9gjUy>35zw%d9Bbm#bDHMy%mYbFxasu%SzH1c6sSW*=i<|f?8cz4UbdAZ!84r0u5 zDGJ9F#xSIZTo;WqlAqDE+)5$ zTAD9}RNGvCZH)0htBz~aO9(rE(4C*i`ry2CzN57-XzgU(J!wDuhC08R*S>? Gcli&x$G?aG diff --git a/tienda/__pycache__/__init__.cpython-314.pyc b/tienda/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index f69534a636df10cde062ab9b62c9d96327c601fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmdPq_I|p@<2{{|u76<)WXFpPQ2KczG$ Y)vkyYXeh|qVi4mKGb1Bo5i^hl0PI*P3jhEB diff --git a/tienda/__pycache__/admin.cpython-314.pyc b/tienda/__pycache__/admin.cpython-314.pyc deleted file mode 100644 index bf9e0c8a0b6e28708e9ce8dcc76a87ee32acf27b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4731 zcmbVQOKcn06@Bv~haZvpQIZu~zlez_B~&82NSdau0^2p!SduewT(pfe7?C4+WPa2) zk8M?NSi^|hRaLY=v-Gk83Uu2|fp*m`X(^!a(4s=S%8m%N=(6{|;Yg$`xsC_Yo%gxF zx$oS2pEMKe3KMt^q={j^`5IU*r78BI`tRS*(baRfkYT2p7%xl=UUf0tVcjs^aZUa&ea*}(Afn#V=t(4 z;9Q;I4xMq(iKp};ksmaAUL~pCFYzQ&64$I;!6~M~T)$>9d!t0RxwcTSHf$ceO-s3Q zmT~o(MH%i>w!rKH*OqC{raUBGVkIhui?-{cp!$gm+ue6<>a06ii#es@wNlQ`w$5I> zTG>UKcuWvZ69=m9T&zv7jM?uL_U(wik+dr)ckzrIgNf(=1vA+cbID zGz;Kr9>-DB{BhaJw|3&Dx$aPxp;WPCnp6jKTmx=9&o%pJj9NTlnl1w`v!=xub=Jzv zc7d}SIGgAz|6ulBsbJ6MtfFJ*XSZl+)6Oy%2iq_x9S`P?N3+bai#cmn#5TRPP2-?P z5Ug`<6v$`fAlhHKaS-i#bb}80+Z{2IVHKa|4}n+@>!)B%fIC0Hvsa#~cGFM$Yzs{A?+NO&tUwe&SfEUHO%P$<8 zPM;DSfNi%2NQE54d+YIuT6|(JK3UZ#MZ9>Zh0l_Ew$oEW^!ZLli9zN7W4lsW1@^Ij zKl~$nLI^!!C1&L*!n4EaBrNd0PNxMcb`}?X26uZCzkl;LZ~k_++B5NFvKoKAs=w~} ztlbe>5nD^CAeWsSSId|!5f_pN62-i4Z;QZCVJMuXHl$?^pvlAKtz2uIlIaYnpl<-d z^gtD+i4<|qBi`IDSWcc!BTtNCAZag>J|z7>cr@?0%*;9NR^Hm?5iwhL@(ldxa&3!d z??Lw1`J5}D%Y%-{KG!?A9`{~}g0$^ssk4PiE?lOAsJX{9vw6#Pb553J?R?%ecO~A{ zwg@?gSeo~c@Cv$w1oOoGCXoL=hBzMfN-1@=3%EJmquH;q(LqPAeVwzc_3q;_N)YpSTDq5 zC@cgS6E;);y3V^00a*K1wJ|p8t7X{-bR8J=w&1bAc|FmegjB5Oy0#s zB%`>25o^KZ4#H#d=d~cw z9-s@I`Ds{sX#xq>1Mh<{&YmZt_Fcy*5=_tIhLGrJ2)KQ=%O0|ho8~N20G_G=jnmLh zS^+i!0BeAUh4TWbEx&`_d24IsZAf-app6Hx4VGOV+p@L`c9EF{o82qrc#l7)_PR~& zV%8SbC#BE@{EF`(!EywZC@vJ9oIQ`W7Q9;YA##P!jM*tTi1^H*W&wv&(fclLAVDdC z_s^hMY$6KYM-<$`Vlh&UjXt{R#TQIFE=Jn_JX@@QJvIkjZUhg?@yb-JV!m5C?>I(>xX2bI^Axl4r$l z8SGFb@4&C|z56>ta?k}Xp2KNKZ(zf2J%)^()PG(*M+S09`4Tbt z-V*%*yq12hq|<4rf=b>l@?gQc{yuYM;PKWD9KQV%$NniFf99jpr&tjFE*@ls{9`8l z2lr18b|37`eCy9^`!n;ubbtNeR}VhF{6&29N%9N4TzvG`?BDPI?LG{|sqx^VU39HY zyOr#khtaTyBD|VGs>A&hp!q47@N!r3QyAcfTPN_NKLRE`bRuoShlmCt?%yv~-i4w6 zQ{KOi&LFu8zb>}2R(UrNnBxe{Wv{#&(&5zGmF2pce5xirq@vtXYKX^jn^tk7l+Kol zc$ue&ARZ7GewV5+p&AnWuL2Ds!46O1pALkIvK_$@UeIY55V*QRytq#74Y(@dz37g$ zR?b_r)dk|n6JEqWC%meF7Z1^*L~$22N+g>2E^!{7yVwM{{=WBT%T?;Y6tV4Y0BtCe zB>j_&{fn@=_-O=)6nGr|MYvA-YNT%`vrjG^5Up+H#y%PDT)DYVE_SY5-zOJ3R;vAz z`y_cJ%TjFTlX_yRmY6ysFlmH|9DF?TaAZf_lM+XARGQw=>fK|t?y)9;NmC*6Km(T> zBR;3TCtWy_gVI|&H|qV9wf@N_feCUBH*oo=AFbH+fmCfEb;^piCk-6Qsx-EfsP~N5 zdd8asCMY}Dz~x5Fw-VZuhL2=L`dC7TleI*$NpOa|uQbF)BkD6p_oPec(%k1-eQ2sS QG=(nBA?wIjxGWChf98126aWAK diff --git a/tienda/__pycache__/apps.cpython-314.pyc b/tienda/__pycache__/apps.cpython-314.pyc deleted file mode 100644 index 90828741a03cdc8b89f3e40f8eea15abba8433f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 467 zcmX|-F-rq66vtoguC=z+BDM%FQd~TQ9*B!5qUfMYhafn$A@UYgh)jd?-8n$;16TPBq3tWY%{Z~>5J$w8?h*+DQ8KPi)_exN?Mp0$X0N)qR6cl z(L9%}d?M`O<|!~B?c^8usBAx9i_N9e4t-sThcq2zE!!sS5nZFLcZD@Wx^(9LcP_S! awAYh$lYV@J&?hv1iwM^R9o~A#3wB~j-W8`H>{|6cz0f8Tq5{^We5KZL-( ze|c)Y7Qnj`-(c?9;V8HbbRQS-ZFCEBoJ2(-hPY4^Qz#?-z!1)0N6KlG>+Zxi34n-@ zbQl3DAV3wofC_%J%NKm9u4fZ1Y+OeufyU51m(8<6z(*Zl2zIcKPSz^oE3G|Ngax6{ z=dbSgOD$NzMR5UVaS8!bq!ag1VZcWn-`;ip6ubUMt)lS%C-G0DcGol9*}snh$bgC_ zP||yAlBx__X;M>2W@1Tb!(NwL)#3FlJM=qESs<(sV*qQdV`1Qj^=N zVX}&CnTutj5=-T_p)s`#ccrXZ+$;lhyRXU!1>eVj%CxQ0L$cY>xoR@*$Sn=( zHeyC}WJ(@Y3FB0DsU%mFxvb|@)tS#O*()S#XqHLJSvaaEvcH|*RGsy2vTtDs}g%P z^Kz!Hq-sj)ZR}lQJF%;z_LR$;v-|P!dVI1LpWKUQH}j1%C^+(BxgiSSKm!RqfkqUC z2A?OllJ#I3uHE1Xgzx2#(GSu3)Ld<9&ZE8a7j5Vm?SAiYx603^f(C}`QMDFTn+ONT z_Ty)_@{OTmSq%k3znkHc)&`ERm0|5KmWJ}Yxtf#Fr|BC~ZUXJdv0Pf*Nnv-Kh|Nmk zXA#3V4=*yXVjHB)yq554yNwPZ9Z~m7cY0?aTrHDpbOfgEPlWZu&=4`k-_U4NQgDC{ G!;?R;=ny9W diff --git a/tienda/__pycache__/models.cpython-314.pyc b/tienda/__pycache__/models.cpython-314.pyc deleted file mode 100644 index 664bcdd572f4d22627ff9440246769f43a9c5d16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16229 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 diff --git a/tienda/__pycache__/urls.cpython-314.pyc b/tienda/__pycache__/urls.cpython-314.pyc deleted file mode 100644 index bcd2369a6d9e2332aafeff61735aac3f509e64a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4842 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 diff --git a/tienda/__pycache__/views.cpython-314.pyc b/tienda/__pycache__/views.cpython-314.pyc deleted file mode 100644 index 30f8c67b2676a140a51f9144e01fa3be47c60a04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70749 zcmdqK3tZgSeJA=KW`-GXf-TcRcGSew{*Sqy?Hur9~cqOk> zXZP-Yf9L-k^ar6dZtv$FiF3}J|9Sn-|2%&0vpzd3lY`q@`RAdkZ*$yV(+|bj<;b0$ zbdHlY#sK9ou!*QGdeOOnH`RZ zvm+~#&C)ELt`2v^&EnS1oQ~W`F5)IWy)&=F6Y+H9NAf!gA_X1Zh_|CKQrJ-xDe5SW z6nB(FN;*m-r5$CFvX1gdIV)r9tmyDXd@OG7tn8?YRCQEGsyk{TH63drYdUHpwXB_t z&bp4Zk+m$I*;(JQF0zir9i8ht8X^rW?(A&rXo@s-G)J1*@2t)Zkqvma?9PoHd6B%1 zmPiXrcXe*+*c{m`=h+h3f;?HBTO(UpxGl1ch1(Z#!Vfj!DsYFc%V)Dp|}h%dnSdfE84@r8&NvG{&|k}pQg6lv#6kXp)8 z5AbD(m$Udmz5;O{i+Avqh*zQl2>k)5Y z@h-m6%SF2RCZsg8lpg*Wegk5r$Ps=cQd?MRFTV-#%`ASD--7s77U%hGh;L`{WBd-p zcd~fjX*a(Mzjo8F&GP4p97pONq&}3A+K<$|NPRdZ^#oE|k-9G>^(0c;kh(u5^%PRu zk$NB{^%0~VL~2J$>Z3^QMCzfG)Pd9M_`_aK?dj>$ZlrV}U$;J=`uq&vgJ(zRS;Wux zB7T&`ALDt%kFj_FE%o8oaeWPuvqWP%pLH;DtPO)-B$ngk%J(`juh|~e3o=!1K*hOzo*e@^qR-keMqf}=x0A!!$aLm?qJ7)j;~2P6KmvyUV09~1nWH*BuA zByAm$$T*@&2SxbclM}&kBt9Mu%I6h6JP~-Tgr! zbdLHK8XIjJ8w#QW)<_`yM7Z9X%;;@B*zP~p`AB=x-rw5i=Ue;QlkQ_3oxQ!CJqP`5 zM~?UO@u!l-tzF&id}ph__sFrn)-I&)Z}<1|?FTwfCd-v)-N$=6+d6w&yE^+i+mHG8 zcOL8QYCT1-8xBSy=usGP$6#1E=MO#|yf98qo(sF-ZX$GlH=2~cyn4c=FZR4@FZz{DljsE z)`VbWLZIf&;fR1B{oI&vArJ{uV^zu4=JR70g3UvL(NJ)td0ZHy=@q8PB}B&5NQ)Y4 zmb%^iR48~c+%$eE>Gb;pqoZRHOa#AQpe|SAFI)_7iu;7Kn({v)@AbAt3f^pEzwY2E z$6lHux>w_0sDsaexrGK5myO^@OVcB(_=3J&r-@wJs@!pM08^>o>X8 zT~j$L$I}L=2h8;<5T25N5G|T{Q-9iK`Y>?gX5L~{f4{jGFvn8ITX~DpvtB?SOP#Vu z_qvuo;qL9JrQd?joSroI9N3>s9YhO?Z^GZ5$KX|Q6&T1q?m!K?tD7;#1KMv=hJxp| zU*tF$jPU`CF+I&qSu421wGyzxo2t1j=8y?9J*|6`CcI-}G&G1gKNN@rl6KlwXiK2y zO&gMy!SewjOrNFJCjj2zk{P>)gQLNx$AyQI>;0oN9Y;cwLI22@wCMej;M0+&-Sl%L z{7{pcc@4^j(FgA5crCH)vFnqwk#9X3-*fc+Jx4{pKep$D_^3a+$N%ck?+yK{$-fWN zSNwcglj-%$Cex(r(KCU@$=1e4HZ<-S2sfTintVx9W4&G2#(JdoB58Si4BKIv5X6F+ zI7=&jd?YZ)_Q{Lb$gJV z9+bnQjmB&mGkwJTH}e)*_;ru;o4rEHWdDHl;-^98k9cufDPnt)9qe)vRj0!`MWF|h85DC*v7iiq;(}XSr!U1?GOWf-Z zh5Z4lI~4Hizi%IMvd>24rnnDs%i_7!Vs7Qf+Kr+@{47U49q(fa~$XEV^AcjZ*V z?whZAc+qNhreo`{rfc0*zJ=P|Rl;QI_tNilI7}FTuIzHO$k|r6(zH5CeN~_-i>dh| z4&Ez=7bzfU3W3&L_@arfojaYsqiQ8Lp#*AjCyiIyhq^C`29W)IuCYFKkCV*t%2 zItSQU$|e>jQ#FdMG^bADd01Mi1fi!BOrHLxmjzmHkcm0^kZgatiHLrG(w%|ov{5F5 zwu@;KL`&034p>+l@RX8DNT7h?8mvl;m-k2HZMAL4_NF<~^| zn;6Am2sCcYht0v)+28650J<9t42}6N`GEV50R^WY0bdKwN`~$Q!%a!k#!oF9nl@~_ z4qQDp%k&C{mF>{knG8zKpzp9mvvkS4F>f)R5495mg=V?)&Eo= z;+NYIol~3*FvhSJMtotMzGIW-#?a88_WYb6)1CvE~S=Ba)N<>?KfKusS0G4JGIvp^*p?)P_P~ zPbXvzODzx0~_sz08; zE}FkCmftYdv*0O|4wky8r|xFH=&6f&4#qvbQBQBo!^b@*qn?v7&m&Xa3yz#Cbz(_l z!d@blH6`qxD~~7ag)@kGXUY~ctQ*qra8{=cG#9H)TWY`Z`67hzo0Q0oWsF-P-;E%}Gpl0Rc2vW+pYLU8X;8G#|%*z=DK$*KcJk;?m+`dzG~E*|J4imdZAHb!vY%=l7|BINeiOEPjlo&8NxyLw6rSi z%E}ccQCYn`X+GHAmo$g4j%YJrOHBA2#eRc4T7=R{N@puc_yQ6Lc_eDE8H7&ZGUfP9 zcwyR<8PP+gdRSSr!aqYYn>myTD51kSwLf9Yy5hZ7askRSY1us5#`8f#&i@9oMkBhG6sg4DM zPxAx7FNybOPSR*X&z9K z1dTyEZYkk3*sWK$VwK(+9pE^^)WdPy+HdVuk5{D>sG#WsG>4fxmEM=e+qcn(!j0P= zyv077Q5$@Qazu~Ykb^+MC63ReZ#1-%?PYXgG1pt6#I%ri^r-~x3U0uoJn5x9c8RM5 zrI&Xqh!S!aDH)J%Q_`!sK3lsf2oM1KXr+?ll={4a>oeEM$-`{7?c;i@lzdv)OFPXH zcSeQaE4c2G)tBS5l=*;OuRI>v7*QgUEOAI(yCexe`z!-Qq-Cy8opYPz*^jnV1f!QI zj7wba#w7{(Y1|I)>fL0RSAoZL4^hKb-A_5Hd>tiB`PSR-&CkBI8+949pivWtv*;`1 ziyF>TC&%ZssE9RM+@-X`=PKW$TK_#%uu;i}T_dm0)TaWBDZO59hNkP@ahVya&0L&x z>ddU>eEMA>TkC}qOXr3jlHOQ1i;Q}0{q^^HBCQNmxe&L@T*iSj;z?SF^_a8Nb{#p`+2cRZ+S%2<-^a4=SrZ2G835CE&jgsQ zU=2h-p8%Ta3ytI11WneqF+m89f!GT!o$&4-6Z^%-uvoAh!@+mI7VrVYr95Ebg_|ao zbDptw**Op9jGby94FH<`3Ti}${yBnVS{pxEzhbATKj4CbNgEuRcpYVW#(d$R4>U*6 zlNp8(5RecKpisl%Rxj|OeY6E$jDnVU?fN=@vA^#o-E#f)g7W@&hv(BCb1_TGEHCF9)YM2%gc9a20h6=g2!x9)WrijKw~QbN^T{ z+%pzwe;Pm>vG2h!2DfZ@m^kquV2y(QAkc!qd=mtGbvo1pIyaMfLU0&`1jbGG11gJ5 zgp-!g=((|ECW!%1HDGty@VUZ7WX%Nc8|?1!5IFj2p&>R$g&wMoD0|65ynHAifR;+r z&wo}Kj6l)~oG@^Sv1*c;=R!g_;-{&V%oxE3l%lrq1UO^iuwX$|;Jgw3BsDWsU6QMYf_8*|rBTN8Q3*LGjs9nY(d=2g!gisdy=+l|r|JXN#XVxINW8H?$e8QT}U zCD)$4`fS`=7xmW71x0UN%)5EIBjK%xd)G$2Yo|Nz7I5y;nS(bA=Us=UEeo!^7oWZS zY}{25byduH-YUCZCb}wOu5Htngu@kgR7XK3kIXyPFE|V1&a$YpEaBeu_VK&sw5;sA z>0DO+i#;#&%xqiaxHX5(cR4Pr%e;_RG*c&5Z;j<`6P?>WaJXWQk_BhMm5cMvHJ_w& zo>F>U*Hu^Cy)NorH@ES&yYYjRlx_3wO&@B_wI|%3YqqO43No)|Cfr2}ZqJpIU&+4f zNJl5Kxvbo{b6wQAF7DhAb#A!f`9|^Air+r*?K9szb8G)kj=Xn7JROLiejou0U}^nGXP%!#?I8$;h1``TE1YrnX)|CZ+`W$%@VTTX~4PKvds=ADl$5s6K{$+q}vsc3E-FtGWDyYa>l+L zb+4*N84uW_Or_-&dZcoEj8GjTqz3dxhk_#Yswjx^zM8y?IzJ^uY2~qYKH!^W-T%${ z(|W75UMVp?TbUuL+^+HTzVvY$2w3)k2Op7i-t|Bug71xWXLEA>b|=@8HlE&Z2Wd;4 zuRv$*hucBmTDd1{ly}m?eo%w^%|NBy+vt#n8_yWfMqc?O(j(^cIsF+%$QM%A>9WaD zq^d}l_O#!sL&}v!Ehg3ZavoLF$j>sZ%GS- zrN4~cGJrbiR-k@jJp&}VFu9FyKiGM!ubp2G-d9Up2!$b}H+WtK`AOEI3lJ{>xiWws zCYTy}oUv45i z5fEg?97I}!c-4C_9&q9w7OtUWy@kPuI$-h*>d02LBL)D!g8bYI{7)Q5;qScG#;_9~ zF*Jr)&wxn*qJ(=E;v0BTrSq^pLe#)FzJq1-cHfE^Fv53J^jT!GaKg}rMjYzil@)cJ4baz z`{$h}mKJqM!U5$`M>z=Bj`EmeecaI!b+p7Co8NbA*41}hJTWluJg=+ok~>mD5>?+`MVqLU!(pBbP_w*_F}k%DL@gc4aJkb3A)jG<#P( zd(ZpXdlJQUGZk~*xeU?U0yM{&cjd|7>3ZQHP!qRD8u+@Xt1gjWmdN#z?=4x(;M{A$ zcyeb>TYx-dE z+nb{Hrn$&l7q4HOw{KgpyW{r4_rclniZz$!?a%0@f(-v}j5%6FON#~>n=qNkeMaE< zC#YHl&uyl%k2rWQ9cF>n3DyI61U^gPIl8qR)qX|r9H1ZrvXvTjVXu5h>yo%`6Ax)$ zfV}!dIsPlRV}Jy(cG3n^cDW8nHV+U|&YYHtgBXa06O}01L5C|gB9it{NLScQ)h)k?o%8h$ zwwVcEM+s>+Wv4ySY?2OzoB|PGf$iF}l#A`!w1-Mm4-4B!lQx<1ByGgBg@)YA^na zReProzSwoSEAA|R-&uaMZ>oL4T|RR$=B}IST(G#V*yb&T3l?|Wk{`9?iv`=?uDew~ zZ|PgGIO7)2`xeiY6JmMmyk*~lBk#(=n4?s*lxlNZ9$${S%;q-13N*Lxqo+U%k~xxz zyKa^wme|4aI+7tne2=f2&S6G<}&x2&w+UZe3@RJxn6_CVJJlcrY*DpXu#l%n8mz` zeHuyT0V+>dumrH8rD~`qby=xNTqSp+8u+B#78Vl$3d|8c2{{jQvq^bMavqa=`f(5k zNFaD92&sz^3P(s%n6!K(@hGOf0+REnkB3SUSjt;`AkR)qJ#H~nXL8j?K zK)B?G4;>&IgG9!Jq&pl0fH9;#34`cNpFY%pFQP)QN~;n-+9v-5!I01oiAqX)10#?o zvmp-xA~)2PMo8`iK(`P)7X;+2rkFv}lsMGV;$tm>&`8QS{wIQ$gj=XgT7Fo6l7Or7 zK4ZdP;31oFFCaql%EdD+Gc{LSQ=K>lU7i<1FN9_?V%b$w2S2c7fBv}-bBnGuUu}-( zZiwb?0BhNQD-_E;8P5$xb3@|e&&F~;hXtLtFYP1dFLK<0w2m|cNV)^RGi}<2dNZ%C%gl1rInYXUwN5Kyo3Cgo>}B5 zeAE093-4WdM6+QVg(TSpEII8n6Z3%h#;U|Tu#&Yk+<*=$4q_VWie4>c3u`k?U7ObX zl-J?uN}CE|3I?@?Z5vU#a+DHU*l&e+Bfbo|6FB?8%FdI1aYR+J@Ya4Cq`cBoL`7^r zrxHw9L-+w=`0TWDg=z9GlQ&7JnF39Fwk1o_AJo3YnK(x=CIZ41RL1RwCGwBb8UY+Uv#x0zf5snNoEASWzGU0<6*h;6h``20{ohP zwR6T8^y5v+9Jz=yQONB#G3qX%WlNmVc|tocxnYP;FksZEoO;GcdC6DiHB?|YLqQIM zvkPY%jU98VJzF8WsIEWl{p!sVJ5kM%vqTyR5`Pr9JP{W_vJe2U`N5FKmexw!Cfo zw)2}#v9Kjp*nT-{+Op{6obIW^gq(RL@hC{8uM=JCfYQBqXCcoOUdvRQe37@Gw;f?$y-cdygOjw%9gb!}_=~JV;XOtK)~Pr`IlERer+d znN9=XBmYgHToh{kKo;hRgvXZN;z~=8db-4=NI|K}55rwF=^>M};c7Vp`aXrM=XhavKr#d8;B# zZ$#c#mOPW!&_>K)p=(ForZmWI;Lv67Zy7h0aa|V_SqeyfsQ8~I0mVw-J5z}=9CA9@ zi}Z;{@E*#d9|2HH`tyTK8i4_eMB5^g-cT?Mc=yn;BRyF0q$1Qv`m2!qXZ-qrqCLel z<*+Nr6v?J!URYLHSM`fRLzC8t$hpStlWEU*#^Bb+|Qx9#l&q(?JUNfgv#A z&yEdUN~S~HA8Ly?nz&C$xuH%`PV_rxpP#me@Z zo>;+wX@;$?e{H?k)E2ASFFGqev2)IfzjWm-TFh3@LV2ZFwIx=*RkWAf#eyM9aW0?s~bL{J6U&>aLlu-7dOz+~rIe zb&0Cl_nj4sW<-B^*Ue=Wyfi5~s_@9_NuoV7!cW8Xc&~rCao-;99d9A|I~{F4?mK(R z+RDuDmYT@-<+N=vzq{E4e^8y(C`I5J#?o@h1xcq|s)s-(p0wG16$^0a7aT z4TYjXa*gUyEUkAln1U(|2IwVrJaXgdO#lS@t#0nX7_o!;?MBBhm^&&k3O0%gdP;SW zuhy4&Vy>3}y(LaBIo+#*omwVc-&EvQTg5CAu|cbs@4g@KjcZ-a%Bd)$c^`W>58 z3J-MBpOkdc2h<&{xA>kyK!=rGX_v`wnn;^4`_&X6$E8Y%UgEPXaYigk?DSPjvf*cM z&An;L*BZh&Gr-B994Kk#K4MZEJX^6&c?NXnqipTHZ5y%WG>ORmbmiOX-oN4AS|BTz zW^hW`AUkW5a*sRvZJ1~0Iqo8QB)l;0Sa!BGDfO>RYR_q5Tde4YIrPJ7H!$F3qfER_qp~MJ-h3SnoD1O^GSb=zOPt3h@Piv6D{9 z^_((OhOr__xe(06l;Lcneblr_v9`HcFQid=&M$ z$faZ^)S6{AIha(Dm4cv2S6|AY;-rOi8gm_o&;|I@pg=@q=@9)X#oi?E>*RfvJn3c6;rNUs9k46Ycl?;Yx3zP> zKya1t7J1(zkC0+I*hhkqV11T!_%70ekI4I5^5T>QI!6MOOR3vQhorM4jbsi>lT-p( z3W@9?n>1W`+Hj~^gUP&+&}h&v4UVEpDEt?+62`{E)x!A{Ct=zJ5>f(7LZmBoP8frN zG~pRm93ZXu3x69PDRP(1496U`Q~MWk@~;eCd;IF-@q*@PLGz6aaaVV&peL4dWa`k* z9QioALHnAi{YvxH;Y49El$-wAT?|Yix8PdC)dnz|%D2Dm{dU9L9(avRfZrj&QI}-MyxZM}E`(|sz zJ-uS((VyD+k8-%2lDi%*Ge4eD@_t51JfrM(M%jWp|6117EC~`=N*2FTcdg;e4LAD| zWsM16-5bu=oN?bC{1<(DUbfua_Jh6e?2WgbiME|t$o0-RV!1U7d4=)3nrL24qNF}i z-W)IAB9?Et(X!~`a@O48>^Yg!);k_uHrW^&JLa1OBj#ndk2celu91q&#JwTgY?F*axQfI^rgA7g@p9XDAfCh?+ z%0-kF`uo$>gQAy#6xLlPqa&~1q6eoY(njq{yHJfW0MuxwCkJrIBgFvFs0gEyLUxdd zrU=AT1-P_lP${tLRSLSR?^oA}XB%1bBiqUEiZj(naLedfrKuv*gA=WxO*ps&N zkOG4Ixj>sj(nSP1>5!H*j2kJ{NvVpdJV~PG_o)_=*{QclaDWZdQ-Vy`@UjL$^p@cO zA%U!6+KuG%S~{y8uw#O=T{@uis0P|LPs5wy7A;lQ$_0h%v?iLnX0BMwT@%aQj5F6& zIa>!pH)Js&3*x*O`WsUK&UD2{#iJW{GxJVTEX4i`O z>lbZY`R+TMr93lgFP_f$#0e_m2XwR%XL)w*ZRfh%&T>d{yx4QOXWj{^7gYjWT-$zg z_y;5JjNCjU9y=@64&HVS=>){zD!pDhmo0AX7Hd)P2t-prj@)~BZ`?r=Fk%H0?AUr> z-f>_d4`Td9Gn*3-t{;k*w?xZZV&(W%^vb4Jo{m>+e!qf93>AA~B@ZpyES1^lt;Yoe zDOs+&=xrg+aaWCKuldD?j@-DT0!D@Ia%mVfyZgoL7qYKh6n#79?K>68lK1VtMJpbE z8m2Ao^X-oIBJO+HndBF3JCJAoey$0A%Hc`7I?f{QP7S>K9iFQve7;PdnPw-Qj_G7R z0p3f`u;5;*C(TTDECO*s3C$vgwn?Q}tpWoaM+^=aSf{d}$P#BnlTfzE6jqXD($`H^ zCj~}Ir?n@k#}ETq-UU?Uf12rxtt;&={{wA;EY8*=ji2!M$TCVqp^ZonD-&Acr$6mn~k*MX%yIJ{8eBey*vX9 zW&vFHuL1!2Dt%%F0P>jDe8jZHz7FF)2<`=d(xrY{?))c|W&jXE$?3jznr1U< zwcqzdF4#meh_VU*NClge);VaV8v#K5mNVS;mpE>ynGUuSZKL!C%;6|@DRC`S<{1t( zx02ecQh}8e85q&&Fm~6WK0@M_xCg3FHPndK>F{Ds6U|@;xI9Jxkqp2f-2txk-T;7M z2e@7z(x9~^2e@srp_TEdLX*stoBrqpUyEj)o)Dl=qSdozGQUkZtd%nyl<=m>3iSY$ zmF_@U(5>Glw&FfrqvlFEW|y4!_1V&bX2 z2lt&F+uAmn-)%9$PdSEY{C?&AODLq#eFs&ngs^=RnbiHtNeCM}+OH7Meud!P{fZDJ zb~5oO{n_nTSoP-)>t$Q8t^EI@YB^hkPG7O+r)k)QM`Dz!^j zcPXB-C0_3|T)T{v#t2+&oocYLx>zOYM&lKG2mQZi#X`Y$?5UuR@Z>`u*zEcWveSyS zTRTmwSh5MPPtv}OdmE$P#%X85o&%BLsJ(FBUaS)zp6wA^I>o9(^Y+8gD|C9U9F94B zqQ&=%yEgn%R-%a-)h)%J+B=9v)M=6zVC%ZpZQMKC8urzizq7^!KV<=aMsVOQ42=p0 zT4~Y2d&R+md*MKN7qHwR^5t@HAYeZA3_~{A8KKn5npg=Q#Ozg9p3!<^L<5yDg52ub z(>UP}Lo`ni!J$7e`0gJ<6?_b{R1ZS7$wC`XK{)|py3&~hyoy1OD&QzAnV~cqLIDjR zy{}?{ICF3$NNUVP@vO!}Ho|+6K@UtOEcP!Pdj8NCdY$sjHyQ8 z+!7|c#BgB9%M_m0%Xmxe(ixYP(wMLo&qc~}9EGMhpx`-$lHPW0)LlDw@vUdCKP$Rx zWA5E?_u;7f@U27Q$>Es${8Xnb4!SiV*)1)a+41V$m-og>8i3yHSP{MXjHcp(ekx|r zRD|~m)RhseFct5z7yrkbiZtmI?E*vLYn2~rG$}R0BBrv6vB<*ICO#8WIR`Ja#2HRy z$K;ljsm%CzVVO@yQyCO8g;(c;Qh{zB)0i=?jzpJpO3WL-#GUiXzm_uTl5T}2<@bk) z2Pk~%Xq31=L;gTyvQ%CT5<@Wz(;gC^P!YeD`j?c=@5#C9GbFATgYTcGZZT%rdldU2 zdC!q|&$4KyxuV~Q-Ah{)HX_SkQMQYyfvpTzVLZDcnq3jku8n5b&JDjca(zV1u8onJ z(xGVfp#&^m7R~gC4efW#X|D92r58lg3q)^yEPdTu+pllG(H&dAZ_x^Ccy}R7l1oA@>tu-SZdWGh_1GNsdGD1VksCMe4^%iNZDluh&0wmGK z#Sm4;QipDIugXbKYS3g{fe_eIxF23!oiu-zI5yU*Za;dUl2i0!U7s=8(iXUIHpHlF zAhVH%5(w?U_7w>L#^6V)Zr@;t_QymCX_Y14fU(1N&IK6l1isG}nL-?(O$C~xtmesr zcBuu8IzS=@b(3jzO|YK>%W~3V)b`Z7ufeJfbXr}K-YP>1{s37dG@fuiX^UVR10%Gb zqn0c~A>132E%VRYI*xS06p!$Cl%3H8u2Uu^0gI+!t;P>s6WqXXNfw`FX(L0*p0>VA8EP$5gG%okk2(|@P&(kPYKo;=d_gn+gu1S@P${{f}{|Fb>u zD%EZ!Rw`{?DzkRAo3Co`Y`G0V#}qVR&l+uVFzH~ZBnF{*)&AkCHoy!8oL{bQFc{o~ z`Pe@C6k%}Kq96wp4m=L#A6Nlodmn{7+N7Q~3l$4W8@q=`zZ zlvx~Lwb5S2jE&Kn7a*<+gb^E&S=dQMAwWbxjzC&|ljYrV4`da5Nkt!K!bn0X*aTYT z@1bs;Zau9eVY$sqpwktXe!*ja&rc~v8))m*`L>0N~fm7|Tb!VLHx< zF}BZSIu4Zp7Q6zT02a}~sfy}3>X2gND)Mj4QT*|qoGP+z%q?-DWXU$aj(uug~0P z>C5QLG&T&&dlb>zaYvs``OZV9*}LG4B-f|TEC?KTL*N**U-%210P};qYkr>-nxab8 z2%!yb?#t4>eRiM2P_VjQS4fWbsdk|tI($SHO4sJ+x_e77l70ajb*~<87-&@T@`cJg z0nWAZ98uScQdYxD?l*gVoF+UEc0>n_I1YHN?*>>K>$~2D)~}^^FjS1S)u&rqsq9bm zRBOioOM9T+yL7;eR;$tcS>@Q?ulMyulLdkG&jIU=zWb*z zC95HZOqlFrf>GNfrCx#wRm3sS)RW9a56FJicwhv`D0_b^vlt-Dol7#+`>|IF?c|N4 zE{%-KsJgg)5;g^38yCi+0{)L_(thVPl}%kYSx_fah{?2+YR#jP(O>JmODrxWtp{L* z_gK;fK`xRhm(-hS`b&0~h{7iUegxA9|Bbx=i#)~#^H7XIgfyKPJuI0K9G$?R;QiSr z!zC5Ck%|CDiC^ZDgk0#&h1A!C^(JZ1U_)UjnVVuy!H=6Pg2{rVwiH-$nCWfn2I((Q zH>~#$J)N|NE(}XWv-CxW!!Bge#KXZ*=snbL_g^%DN(CcsDr9i4=XVu6Ulm42d z*+*lZjnf&VV+!%;ctLHnpmz3iv4Sm-?KZN}nv!oaR?vcc5S~8$^67Y4Q?#sU?zvbQ zY&I2>S(LKZ%HowE{qiAUD-c7A?EFjyLP?r+NCp=bYk9la-pa) zUQ{0~s*e{nMvEF_Ma|QPWCKcjZ#Ia%kBF_0#_|S4=fI-HmT}s&;3=9}^OdgY_JqrG z<;gEyS}1_b_P$s_o9J#^G;{8v#cV5OBV({+A2#k?AN8)Edq(uGk9l`ZcPwUeMP<`j z|ISecA@5haU+#{VZitp{NEDV{d1~enF@J5MU@fSWWg8)(RF*y64OknaxA*GacwT)p zubyP`3M#IRT^XBgNfcJg7MF_ansvpz z;9K)X)@xaD-{z=qbE4EYvvsy~ZtzCs9|q@6h(%ix#UyoKxdkm(=3dEy%suW)aMwrO z^>KG&)ZI9D;ivB1A33*zhHs9eA?uY5H23u(Uk=I0iNo!HpHuI0S zn&78!f-P864Ds232w@u!Vj4%di9*6V@KzGVc$8LRIjjB??vRNXFXX@S=pqZ>9F%_F zOOzqln_)uuO6c1FR<-0bdL^j2^ae{D_7|ZOlP$xZBPar}zseTYavDL} z8pZR0thMT4?bWxXVM2t{thR?5CPX;RYD=l`DwHt|i7=*@_Zui{Uc+O-?NpB%iiKay<4`O2&H#q%v%$)#Jom>Pr&>nUG3&D!C_Lu&rH!i4 z;VFKU#M_uVb#|1FekuM(qbjD@XIHV9^$d$qQ4PcKYgX#e!ag%FvOHj9c3?+X?c~Ot zr@k|)3LW;P8CgVC#vNBbWT<@r2dI=oe&wN59rIUKSR5dR3Uhtx4AWs>|5kU6hT1Qs z1gIs;y;pu;mI3yat$S0~GQHiU)vm<)RE-B6_N9KU-rY(*EtIh@8sSvzi=;r9OToTY zo?Yt9QOfGDuT{)_;5D?5GY1^zkc@$8{a5Fgl1ml{)8jC|mL39!!RqY8>g?04&U>&h ztu3F{BPI4gZ;+$I!n9hA=Fuw0cr|YTOsu{gcPON)!H~F|Vs%!>c30SB-^2T9h}sfx zT?cS~pYk2RzA{-NBU@mggG_CJZ876w!a?K}I>`G0744)LEmGkS#SW9#Ow$Pye!I6`&N)z%M&-MmFTO2tv6m!`QXp*`QF7~|p57~t|flCctw zQd1=Osl$8R65jJV6(9t~5YanEW%|fFP9CABx?%38v=ii=gcqjQ)#1$OhLtVCJutNt1PT-CH4(wB*fb@TSp zL{a&dvJ<5>V0`{;wa5=pklIc2&Z-Z<1kJjf^(EKO9F+-DELszFu8BL>N1f~EosIvP zIL=YAil4V1)8RNqcRtD(&UX1e#@s73*t>L+CBL`}wJpMS(lMB;Ms|!p-hW{q&xV?Q z2_0w+?@6utK4{PVs+KUI?^(+Zr*Yq_@pTn(KT2!O=_)Y)INwBmQEN%}HuGO>HNpSK z#(=KVDkeH0v4Jv4c?KTgKL`l@L2xhjLs$#LeyXq!tiT5Xy!1~lF_UJ**8ql;wi2|R z!pc=)2)woI7+i1dC&(u~h3;%qa%k8XgmP_@_4{CYeN|_rj+ILSjsl^UlbJelTydRF z1f5w+*I2Qc%d=v&!;Bq6OvIO{XbRg^JxG%2qhlAqU0%X@b)BU*(2ii?EIKwvvqwNJ zDU>Wj0T6|(@uAC?a#AF4MbGKWr>71iEY2?+dH%?}C0{2UJexVUb#ATL(j}I6&s%y( zO!!F5Q6pMvH0~t&!5l&Ngj@fb8KwW4-jOgBS|N5K5x}FXWDsbbU_F2*vq-r+w^#{{ zFrd>agVEeoGeuS9=#-0+SBW|sBfgr*G4#`1O2>gz(v7GlQcQ=rOEZ+PWkLleaJr;# z8$)K*#4Jp|4n6@x%}aC^W{gjOA2E11 zc}~h=9^cp1d90&#Ust>TaQi8yb}h?RC$m7A z4~;|Hi*D5z){B!0PhfDFcauh#plHcdB7t-H^S1I2oZeRo<3-z}McWt6X+@dSSwtr; zo$0<|op-_9*w&9SID3V*GCt$w`6KEjp*^%bx0xP;7z6pQKPrVcW>!*l(y+h>pr<|rQ>i~9mv0{a5g*o6^ItQ?&8IYX@HY#oK`AQ2)2n%`lD^sB4RYH(iRq)El7TIb^ zZM?V1#25Bk`tAK0e9<;}MUH3oXZCJadaQ-$C%*qu#~0Jb#M!+15+zhZw?C8`w+BHN zc^9naK&VnlX(01FLSKE{G4PPmo8DF>riFYN1U~j@zm=F#PY2qSr+unTHsvG-SgYU5 z%8l}pIe*NB3T>An(iLwVckFdk%VEf*>P`A2c=Zp^=*L9H=zjM)(>f#0w=63?_VeGx z+g~>^0p*^LHzC)4+=3K_MH85vXO={X`5n+38HIEfvJ?y)5tTEcagj)@}4J;HDC&cNzOtqG9anpdnK(RzdCv3 zFDAT*vhSq<);SID9(2o9GIK(#-7S{xxjBe?Q$_osMaw4ZW2S|Y9dFmgO7`Jiri7#9 zeMdFSz|4BZb$-$Jm>3$F&$}Sv!co=6O(MVO+9OvViRU*(^PA=_#qxJdXMW%+k}d>| zyQ_t&PAuILW5D(= z791Wjet=JH$MsL7vn>{N-5UPM$a^C{8GCPR-hWmc9E$ma^Y(KKY?iF;_;KE?qdzLR zWfyCYilw~x=$Uz!AKDVXBr6GzK}(z1H(xF5*ulNSwPqstj$_|$1mCl7A^)Ll_}|ak zw;sW*9gag~=JzbE*@p_vKg>1Z#}5mw@KeZGn6RG(Vs6D*rXuE@ z)0IyA6borNu;0*BIB++G!?Nt9Q0lRYR%EA>XHHw$?75NV?+Krg61avFt_13*qV+m? zKNZwc`iR>u@)U(mm{nZ{k0s{i5Ksapji41(V?1XE@c=-TnxaMSTQ<$Ex6!8Zue2H% ztxa!Grbu}Pl7+RIQ-B*rruaPVu!Qmmze%zG6S0Y{@Op7L(S`6MF!}*(HReXi@p8qSvYI#?nd5XdbXjN_)9vn#G8+@i+98jDa$vsLAZ!)>GZ> zJveC@ey+3JLbFF9fqX=T7RaMhC6xpsT%yXzV{`u|GT>H^!ira2FT3K{HwtkJ-`<TQa9w?w^L#2sDnWB%wdzv$f( z^F9{$o{M_V#k}W5i)X=7JhScf_ISo%Xne)C?alK;Rwq1RIyZm(@<57n!*B`*E`amP=?f3?gWOPRv{P1eramEVwRRlK=i5`ZSp<%NK-!%nY8FNZFV1 zAZdMaA}CyvZg6r+H#nU`x}?39Los~RdoFKULq$zTHG>w)uKp^fsdgt+>aI@OcGSA? z8ZxnE@UKw~h^{c@c=+;Rx?N_bZ8kUNtcg3H5z4SAi^FyfAszR>| z&fFI}Ug(%TKJQ#F$LHGToh=`mO<9{hlpfBlnRjmZ*qWBL@s8Qz$;3qxj-ty=xVN-E zmbn>M-elxJl2%gtZ-xr)l@2l&kk1&jpp>^5Qp!K$?V_2s3xWiQ>^o>O9Rl5#1+T+^ z3ya*lX^>bMidpVdVF4_+X~>HHOR1zF$Ac;Y`xgp5@qsA6X}k@k?fTMZz&h+SV|fsg z%$$yTOtf5;wyHb_6qBXJm6%Rs5z104SprnHqkAa8SddG5XUrbu9Xe{AN9lnU!tAdK zVj3Af=CgFq31V8}G{e6Y9JXJpCi19>Dngvm0y^{8L1(toCxX7N2A}?-g@bo-UnsbxbFrOUp^P~ zUgO*bG+?6S@ty&dira4o%~tLmU%-1oFSQwsK(i{`3)(TZq71H_)76r$)dKozTJJ{v z+emS~h%e?#_)@+M^QxS$0BS%~za?&9i;}?ml&_+MeC2)1R4I9tusMyd=4<#h5R9d_ ztL5tuTg%t;>-hR+95(!VzJYJ#oBFNIre<@KsaX?!cJw<|65v!;&d_rV333=w+|geH zx-s8OQv)<*gYUCUi4ClT!-ztSc@MG^?h0`liDU8`NpO4^I~=$gK%{iE-WHTlDa^{K zbDTz(p1@rh)zX(ndC7qVxoPRXFwm+z?RRK^lsZ0W6@X5gmZx{W64yfId*RNO&Er`E z2b3qh?Mh4w`7Icw4(+!RGaSz>1r689YF($#;A>L1V6Mn_Y3Oc{kEN*OKWcY^-E%2DWBZWMO)DWkBY zpM3gw_TFsJ_F1T-?eCskuPECQaTi$gsIND0sTYtDG)90hO3=<2 zdE@YUgeNH~kar6&B@jv?{02PSAUlF<1z`t#D48Wkp;sl~vf2NKGLDdUfjlDZCe7g| zCWLqC=QzAcb5qMXVS=hLw;$&v!{g}^8bxez=yzE`V22fhMJk+*IF#g)S*+u9-7To; z;bbNnn-E4BIh}4@K^A}TX{f_N-HefI30+D$VVN1W6b4C;FpR5C80SFv&s49CF8d9k zCVP0|taSH?ITCm}Y1)7k`I0lHdN-NZ#>_VmdZ$@P%V1WHnWo&|Q+0IH3-tj3aS6!& zF;Oy-PTaL3jNw)_>M7HFo1nu9yooTeN(dMhBvbOo`7UZAfb3^M)Oh!gM?cMxsP+UU zkByEE!sN_k9f%RVtzCXl-CO(GmnZs97A~RqL&8_0^d}4DDvs?x?C&|!=kM%+L7J|v z_WhH^NTNRUN|Ob=Ai(Lk5}8a_{5IZZa{ZFlPqwx9_JR3e#CG`9#(J>RSHE@%R{?h< zv%!M2NhCb4v_y+q63&uWPR7f2M$2}RHT+DN z?#OVE`eDg*) z4;+>9Y}OGmBB-oABq#-s^o)BJHN5>Ydfb8 z5;_c`V-!TkhkD`<9gRM8R7u+`(VwqM;o^BX_SYg{O)n=8K2 z_l;9uJ0-4#rN@Wg_dJYSfr@JH+BuJpIATXdA7h2&`9-H65>3rpZw$YEG2V3W{icJj zm}d?WyJ)^}o!I@@bSL4_U6;GWqJ1|H{-Eoft{)tE=ZJXx%)HYtF^?Md-#m5mu(&RT zhlHCG3W!s4lsGjnIxjoL{N{Q428k(CyH6}_yVW-D>V4nd%UC*QS`HrXjOi-}rZaTB zoddUQKXJb2{E6#5mw4*zynT>WFlW1w0X^B;U1I6(o5$u|owx0szl1_3D)}^g7>oO> zWnCuX=VT-Jj_V-(dT(PP{2ykz$uDVLgW$)e=ALZxk6Y~I?=q3UyVcf{Vg5H}`tffv ztnmLacsu``_KrOCo6!4>rkan)Bg-@h?nUhF$cO@#JCaCO2?ylb)X9yT05+`_jn#%oKB06&i^Iu zRIQHFW-*?b8n?{&Id1`{&0+lPzGWTYv}tp2!;omun91P^u8OM_ z@tm4yPK~%`dn{)M?1Msk@R`fcy!hPZ=VmU&T=i2OxGD41lR7(m`{wO!q*Im|b5)D> zYP!v<<^y~FuL9mH&~ZGsA)4DT_ed;vJ0MZzQmvUob9S+0W8Bg5zN6)Xs(J#*<5gRt zRa?ZZeX*+JB;Hif6tCDCt=Jl`*b}YTBewR%A03W9IxOO1t%~#UiqUArXslvfq&vQ< zX1BfB9&dO!+VJqrb$}hwj=Oqx_>HmG#^zmH$SBE?t4G9|ZK7v;!qs@g`;CgPRm{5{ zRv+vWJ#D(Hz{J|nyz_C{{7UQ1mRQ~a(Rtv5(rU?2N@KLNaV`XYf2_27x?9tmt8Km6 zc(X#RIXds;7xGGG4#o27L}%SE?n3F*=_P=*MmN@b=j{(GWBoqH8WKY_zr5Rrtobk& zLRnQIq=UD-KPi2$^d}YXRm?wfT0G;AJ@VMRJ)l-LTlZ%5T;N~T&J~KjmU;UorP|x} zY6iL1NP{3j4Z zx}^tJ^GsVm;^4jRV8Ok>Dlv^2u=;;v9W2L%G89C5Al9{x{rq)0wse{XD!>JUD&K-= z&+F_t0nyB@7G&1lwgPV1Qn!RXtpe0W8U-w`A$wW@4UKHX@>XIrLj!{LPdFo1v~qg$ z84Wt1m8{BwmX1UEBFYNw!Z7$4Dw|rWjeO`qSzxl-3h=r}N!1K%=($yg*UEg75+xs) z+>oPI`VZkG#{}^^T6;Tvt%q7q`oLcX4HZVkMgzX^1oli2@kjy*hw9mP|1^xpLpXd# zeTR-6=@DM0eUsir_&tjK3-X90D(%)p4W*l?VfRabt*Z;zf$3~~ijpBaJUomumlkciNgWE)3{wsSN!m46;(SA9TJ5AjhU?ji`Z2QKj|-mbnK}%P_e*(K zj(??i=E;{T?#ugTtZwkW!Rj`H)t$~c3NEj|a&Bg5_Mq6fHNPuBA00|O%YRBvAwj<;Qg|+w^m{r z(#~5_cOKoYNuPg-dw}s(#d3@Y0?Uk1x;jP=(r1fyeE>vwu;W5}M-t;PLyX4)J}OOO zIP7xH$%_4f@cFZ20f8>F1>g*-HQ;A#YqDVw$ff-!T`KNNRM}4O5 z>iXxYZ$x(&rpROP^>Y-XLxSM$!I25r$EMeYS|-~B|2?`V{0H*>n!FF;Nf&nc;H2Fy zJGIxs-yqKiv{wHX^}vb-NRdU?_FUZ)&#Q~()ybE1D9IW`kI;IaY8mUeFyaz=iThrjCGsnO}UZ`)ap*JD+qJ4&BhV zJML+ZdfIPZhCg-X?vUmF3~=<*;K+A-7Y zTbr~izpxA;iiBlI_dmI`x|Eo*z3Q!l!3x2~E?DHmWz!N~4TY;9i$k`QEmZ@{J;J|5 z-y{WYTB?&?SzeBAVv?-U%a=Z-OUp{2WdOJ!A3f2*6l^K|kkZ&HqIJVomaL8}uzOo* z3MiM0IdlyO1So$(AM!MEFl#f}d1Nyno>dvms+_aE<-G0`vnpd*TjE&v&B0kqn@xS#3D#aMd98}`@ib7e8#Ho9UUi>_s4*E3RtbBMu&2vxnf`sgBGNc6=8rZ#nE3GMbQdYBvpG z20z#p1AxnDMZ>Lh7vlq6Kww#AounWmw=MNR@{N`&Fx%Dj(|lrsp``#7rq)_w*H>ZG zQUuXetX#`y2u+%K4RR!|+!CiOT$O=S!`ksE*^FdCnbL&*rWOsuhM2hWm8vG)fRBbNDI-^&2s(l(z(%L3=ZwT#YVZZ>GOAp;~#UStx)p`eoi1 zbbIkEq!^@HpDT&qT%|fS5&}EgSz`o;=+68 z#-!0Bs80>FY2!$!0X0&HnyP7}s#K(EROv}O%qHVFNy$^ws;bm#0q-M8rPqGv&hOe| zvd&BLM%JCX*Z1BzGjr$M@0@$?Ic&~nG&1sh;C&Wvc1{&UoByXC=a|XorO__lK@a)F zTb3e|W@18~w2MN7Rh6O@QqhX3;j3HZq9&ziqg1p}F4{Dit9g7@16WV}%4wcPhaeDOi*6)Vy`y+bvgGl=@bw zzV+H;@%mP|ey393CDnH+_4{tr?=#h{%MT>H2Q?*as~p%A_ilpTw|X@h1SEut>B>4a zS3X@t+F$zSn| z*zp75nL2tA#2IM4zJlC{At4ngP9_l=4IC<~O#auIF!?7J%j9)Dy(l(r;_SsRYSU^U z%Ud^&VDx6GVW&WflJQD(r-da1pJx`FJ)^Pf?~q((xKU-i9o{9F|B5l#*Nu)o z3sDOZ`(n?Uzw()tqj=OP8OMv^I@5bq`8yV5F{kMKJu2P?XJDqJosa@8M2xaMh$zDv zZr=!nnCOoki;C}{-|-rvoGeZ?dd-=Gt_V*XhDSO!b1+t|hY2&M51KhJYh@C z?u2)LdQH&a&R1uTN=c}kT9pv6gDZVLf%eB8c%|v}&WoK1Od!_Tg(AJD&n`c{|Gi!G ze)Op&heTy0mmn(RG=FL#W&o&x5M{`Gf1)L4+JmjvVrhFX!$LIq@#b&VlhUB z0veC`DN~{EcPdCOg3g=_?|m^b@?07AnSIC*)>vG;OrNcgVgD3iBGWz%PySuG>$hWD zmSBmvSGd2u+Z8peLk*m_p5Z(io9wW&Gi9j4W5FOM`r@#`(!fLZlnOI#CBsMViQ}c1tYFaD@RytQhVg z14G*PMEH}fS8N^zk%)p8O_5dm-VwOV(A>Hlg<@Fs8As;@8-{E9uK#ua5Vo@+v>UBV zuDS2z$&-;NG`w%oZ8`Ayi&>O7DKT%=)kp5);1eL5%5#v6htXiG`B-TyO#eItOq1;b zx|1@x;O|i~M^Ewul^vvHA0_6j|1te!bY7txrZ&S)Zy~}(57BE7JxYU8x5CgCCjCUY zh$!J2SP{Kw(oHiiHJgWv(<742Q=VY|Md)Hk4Gb2qd+@z2a>*v8q+Kd$mrEX=^i6-; z|I@<-Ty`LCxZ+%Cq17PR!zFTMSn9#IV2Y~ zULBK)nkRFCgc0zpRK8HNnyKd$f8z~*qovt139oq1W`x^|RVznD zsF8%4>4M@{L+3+?aW=gyLVt*N26tn@WfbWQPR9bAd*v*b>&~4mwnQL{S8YuQ?F{9w zmi^JV5Y+(RhD7roxw%XB?2SA3ng9#KnH@Ct2zuUGgMBpg%qIEY%v=Dt;2Jj(IBG%NQ|p-R-QKsh%hxk#9L z(}L6Pp{`_LtyB&F#84N*T7OE{K2M3c?zJG?VxTS^b7g|@(ig<8WT^{cm*}GKP|l6g zREy|LswG+eTud$I<39huiFzf-FqKY0)uo23z5g)rcN2;H&NCfp5t+(Yygqz!SSgQ6 z<xldgh5 zudC=>Lf#BlU0kJ<)=8yxmz%JlSK6eMZj?$l%B7pom4CXT=Jkn-6G}ycRMBwND_3m1 z7L+R5sc}fdbxLrh6kK_^M2T#cBAc&m_~+JtY@H5-lt8T%sJ$F{yWvVhGK&v3-{tuH zmfMtIdxfrrod^DU<+eUK&>#2q6YO)p>3hSc_#1Bc8!Q-Z9`v&|s+l^atZb21wkZCM zH~bqd&E`Qrv)LCy!8zYqUqUFxMhbHoup=StSOCJsAAMYI{)y~46n7r_ouIhOG%U&b zo=}{dufIjfB~Ox0?*`bf`3TO? z@X*nrV?DOs(i#6$xfIG{*!`ku|~|Ay{LUzqUF!UPgp^Tx>}0@r1SF`(bY&mUr8 z!fvIRmL~v3wV`S&mKZuXENobyE^V#p=F=87gwv~bVLJ#`{ldf0wZON?XwTDF*I?#s z@F~MqI8MZl^~d1m1<`+av7W}^YDzxDO^dUXFf-C}-HcT0L$f7CJ8jj2S(6@S?(ZWm zkzlCBvV@Jpf1xtNnp9u^SVCix-nTwe7V2c0mk^ht!n^eH`cM&brNChlD6EzWtChk! zsjyDd55W34P>-dK0IZ*vz}#+HD80}zb@EP@!-W#x^MjK=lXJ?FZZ13DaF5tLEvSnm zt-p&!k`K~HtC%KGr2SCp=)%6+(Xo+m zFT6$No>KFqD@2Vj=?W=+1TC(`#9T7BKfScS=h)D2PhZdI(AdcOhX#(qLsJy?%BS}_mf&o=3B`h_zOW0?4gs4&#Z#&tTY)boMT8}m}wVe}QJGmM3*t9-oXcz4NzsIl# zH1ENb*3iZ;O6i=2F4HL}vSRCkYYf zZtUr)z{6h+@48We?~v)GbCSL{jtL=JW4^g)|xgp?m{svw!7QAI<_4=yt%FIk;iuL zV_NbVUsr5^1WIx5SkKrvT(@m*+t<UPJ-m-L@V=hwJJXhQWMv=ZKl7Cpl#ev4GUMXB$Q>N_Ue&TT)tJ>jjL_WCJz zZr9meFF!&*cAVYuGK@i%U3gdyERTDaBW#6tH;=uB`Js4dzwAE{7Y=+iTf})oil00{>r-;5T3{1?J)Io$P}a9bqndM%Ii$8hcQZ+O@SY|18ka3r)l z5hs(?fjV{=lho7rxsV)Sw`4nKf}(!d(Pemm#kXN#ZRnfkIBI93@pO>}RKWBtHc0U5 z%rFQ+IkpDFc7ln20w!m1w?5-bZza}a=&-vd#SDfHVkA#IKcTpm<1g-74*BhZOZJE3 zLKp(8YR10~g6@xN$@yQ_*TeITw>TuffY651Jzfv#1g(m8JW$T+F($&F&exyi!0~&PBP~XTZ@@+XtyWmd@QPd7#JgpM7VTcbHFD6dnQDe^;6JFVXB4xu&DQhMI_Y<}} z3>-O*m1RsfSfBn&R8U1_ChShp&pBT&`&wDT2EgN}qJFY^9L&%BMX$iGNAr7Kvj3sD z@X(h+o+8vrLaic1B_Vp5ZMl5!;g1^F@)`xz9<8+==f=n9KqROGIbx37Uk10tbwyDh|T-b zgx>|)I~ivDD~1_4J1p3hZTcBtGqB>`b*o20t5S%ulzXu2k`Fz!v|~XD3z>5!FZlYuooCTHc<`d-v{t zq-(2%^_{1RjH&$vu? zB?7SgfJ0q@C`#w@+QSmpgF41nG=pjYgV3e+os1}V^RbzeNtAO{|t6p{i5 z9k#1Pa+M^?Rx4$jq_Ry)*>0(9cfz$h(fx$neK@{{+#(777w2%@EpXfV%2pVow)1cp z>d1#S*#QTwIb3eRx$?8+7amBsE2j|#qvd?dRPINC`bk0C@&gB|9|aq+#m5s!x1DZF zc(-ci7TZ2t5`Xlu4}EfAf84wO3;$BulnKidLED3Kp*A5@W%h{j}3|h7euI0%%ncLTanG)&J#Vj3dy`8K}(UXs?j$j5+5?|7B_Q5 z3{y!+qvga_SKBrd1qMSLtXZmqNaUa^K+ z78o}w#-b2e2V)}17vg40nke~Ks+I#U;r$~=)d-fy24-9oJ8}pa_dsl{6M1+^W1iMt zqb55jfzAQ##*ZURDTGw|;T2;?M^40!!zJ|4N!9-AAk}0xVS|)=hLRU3`8g%;Q}Rnn zBuYM^ z&LY(?AJ5F08*|yC?)0Fo`fOh_+w;NpSEvgSWKqTzDMeZkU@BOVlw?evR=cu!)~I|(Xo37cJ{)TmJ5e21<3@i8Jc3V%=WlWkN!&v>x$QEy z{U&!n;tu>fx8ZZH`mTfHw>j>jx}M%*`Omq)T}KY@NCC<_Qa+Ba{G7Y*bFShhSNAEG zcaw{t?WdgkQ?Bq+&U2G1`jjJg$GHWHyYz;;^ul`Cz5GlL{L^87_6>h{s$BN3Ipa;` zWb;M0f?P?{o$Rbq$FGVXnDoLSPkRcPI0=d@?3;;E86Ra5sXp2%!A=Z;K*^{@>bD?S!#KMAe7MU`i>Kz9;!e_J;X zD4sg%rq1Z5=3^m})=jD~iyz@rUI)LGpUrjhIVqo$Z%FyFDVN8)Qeh9@%cnxk{F>AR zzkzQ|o#b77St{t`JNQ&M7yrB+a`XAA;s7kPW>-b|f>bZRoUcsn=CM&DRT$(~q@rH@ zF3;vGllkD;vko}qxRZrkl5)u)D{~uV$R&$eSw3H@)k7{>!^(n28FI-eE8D{JkMUYf zmQU_*uz&2}jb166d|WNguX9n%Y+YnMU-wR>8uj|X=xGC{|BIseV+gT diff --git a/tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py b/tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py new file mode 100644 index 0000000..1782119 --- /dev/null +++ b/tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py @@ -0,0 +1,45 @@ +# Generated by Django 6.0.1 on 2026-04-09 06:55 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tienda', '0003_order_transaction_code'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='stock', + field=models.PositiveIntegerField(default=0), + ), + migrations.CreateModel( + name='StockReservation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('session_key', models.CharField(blank=True, max_length=40, null=True)), + ('status', models.CharField(choices=[('active', 'Activa'), ('completed', 'Completada'), ('cancelled', 'Cancelada'), ('expired', 'Expirada')], default='active', max_length=20)), + ('payment_method', models.CharField(choices=[('stripe', 'Stripe'), ('paypal', 'PayPal')], max_length=20)), + ('expires_at', models.DateTimeField(db_index=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stock_reservations', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='StockReservationItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock_reservation_items', to='tienda.product')), + ('reservation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tienda.stockreservation')), + ], + options={ + 'unique_together': {('reservation', 'product')}, + }, + ), + ] diff --git a/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc b/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc deleted file mode 100644 index 4fa3f99749326882d2ff92e3c754b0d0d852e513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12698 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 NR=*cke*r;a^&g9h5-|V( diff --git a/tienda/migrations/__pycache__/__init__.cpython-314.pyc b/tienda/migrations/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index ef4a82c7f20f557cec8e2aa6a8611a2c7e166a59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmdPq_I|p@<2{{|u76<*%QSpPQ
+ {% if stock_issues %} +
+ Hay productos con stock insuficiente: +
    + {% for issue in stock_issues %} +
  • {{ issue.product_name }} - disponible: {{ issue.available }}, en carrito: {{ issue.requested }}
  • + {% endfor %} +
+
Actualiza las cantidades antes de continuar al pago.
+
+ {% endif %}
@@ -20,6 +31,7 @@ + @@ -39,10 +51,17 @@ + + @@ -89,6 +107,7 @@ + {% endfor %} @@ -118,7 +137,7 @@ class="btn btn-primary payment-btn" data-config-url="/tienda/config/" data-session-url="/tienda/create-checkout-session/" - {% if not addresses %}disabled{% endif %}> + {% if not addresses or stock_issues %}disabled{% endif %}> 💳 Pagar con Stripe @@ -126,7 +145,7 @@ id="paypal-button" class="btn btn-warning payment-btn" data-payment-url="{% url 'create_paypal_payment' %}" - {% if not addresses %}disabled{% endif %}> + {% if not addresses or stock_issues %}disabled{% endif %}> 🅿️ Pagar con PayPal @@ -139,7 +158,9 @@ {% endblock %} \ No newline at end of file diff --git a/tienda/templates/tienda/crear_producto.html b/tienda/templates/tienda/crear_producto.html index 37ae71d..87a5b34 100644 --- a/tienda/templates/tienda/crear_producto.html +++ b/tienda/templates/tienda/crear_producto.html @@ -46,6 +46,14 @@ + +
+ + +
Cantidad máxima que podrán comprar los clientes.
+
+
diff --git a/tienda/templates/tienda/editar_producto.html b/tienda/templates/tienda/editar_producto.html index b181661..1e40f34 100644 --- a/tienda/templates/tienda/editar_producto.html +++ b/tienda/templates/tienda/editar_producto.html @@ -46,6 +46,14 @@
+ +
+ + +
Cantidad máxima que podrán comprar los clientes.
+
+
diff --git a/tienda/templates/tienda/mis_productos.html b/tienda/templates/tienda/mis_productos.html index ba8bbb5..f08e2ee 100644 --- a/tienda/templates/tienda/mis_productos.html +++ b/tienda/templates/tienda/mis_productos.html @@ -31,6 +31,7 @@
+ @@ -53,6 +54,7 @@ +
Producto Precio (sin IVA) CantidadStock Subtotal (con IVA) Acciones
{% csrf_token %} - +
+ {% if item.product.stock > 0 %} + {{ item.product.stock }} + {% else %} + 0 + {% endif %} + {{ item.get_subtotal_with_vat|format_price }} €
@@ -82,7 +101,7 @@ {{ cart.get_total_with_vat|format_price }} €
- Proceder al Pago + Proceder al Pago Continuar Comprando {% csrf_token %} diff --git a/tienda/templates/tienda/checkout.html b/tienda/templates/tienda/checkout.html index 4272896..8999c4b 100644 --- a/tienda/templates/tienda/checkout.html +++ b/tienda/templates/tienda/checkout.html @@ -49,6 +49,23 @@
{% if cart_items %} + {% if stock_issues %} +
+ No hay stock suficiente para algunos productos: +
    + {% for issue in stock_issues %} +
  • {{ issue.product_name }} - disponible: {{ issue.available }}, en carrito: {{ issue.requested }}
  • + {% endfor %} +
+
Vuelve al carrito y ajusta las cantidades antes de pagar.
+
+ {% endif %} + +
+ Al pulsar en pagar se reservará tu stock durante {{ reservation_minutes }} minutos. + Si el pago no se completa en ese tiempo, la reserva se cancelará automáticamente. +
+
1) Selecciona la dirección de envío
@@ -80,6 +97,7 @@
Producto Precio (sin IVA) CantidadStock actual Subtotal (con IVA)
{{ item.product.name }} {{ item.product.price|format_price }}€ {{ item.quantity }}{{ item.product.stock }} {{ item.get_subtotal_with_vat|format_price }}€
Categoría Precio (sin IVA) Precio (con IVA)Stock Acciones
{{ producto.category.name }} {{ producto.price|format_price }}€ {{ producto.get_price_with_vat|format_price }}€{{ producto.stock }}
Editar diff --git a/tienda/templates/tienda/producto.html b/tienda/templates/tienda/producto.html index 47b136c..b9b0bdc 100644 --- a/tienda/templates/tienda/producto.html +++ b/tienda/templates/tienda/producto.html @@ -39,14 +39,21 @@
{{ product.briefdesc }}
+
+ {% if product.stock > 0 %} + Stock disponible: {{ product.stock }} + {% else %} + Sin stock + {% endif %} +
{% csrf_token %}
- +
- +
diff --git a/tienda/templatetags/__pycache__/__init__.cpython-314.pyc b/tienda/templatetags/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index 5c2839f93337cf8329eae1e284d0b1b95b29a696..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180 zcmdPqw}tdn zy;wa}Jm^8egNQ}{0gwI%!BUi^g@SnS7Oe;I{$dGB6A~uP0EKCbiqAkW@hFp|eo*l~|jJI~SG$UOM zb{w@}MjZIzPL$B)lJaj0ACHTPs%$YHJ+l(%C{LojqG59mzco+RLJg(Lw4!dVN{%#VnACP8 zX3kes#uXa`Nj&aQ`D;JuvhIh!lAj+IKhD z5D|T|_+__7M&U&`O~ltgQ2$gy{`+nT7u{gHDPcze|7X!+8O=6Xg({Y5n2Kg7WwT`Lt4_EP{qi2G~*3X4PMOjgFhqnw6j-zr73qNI?G zqF$zkW-W?iskwy6yqE>^amC-F-w6uNxmOk324-ROIe?#a64kF1z1utRP+W=D<=8){ z>FbAk;bY=6a!kCC@H-y$I<1sU#;4o~G&_B4fikN(Ti)~fRYqCOk#Qz<3)|iCG~3?o zbhm77Vym!?H$bzn@~N_^&Kufk7DXF>N~{7Z>}o available: + messages.error( + request, + f"No hay stock suficiente de '{product.name}'. Disponible: {available}, solicitado: {desired_quantity}.", + ) + return redirect('producto', id=product_id) # Buscar si ya existe el producto en el carrito cart_item, created = CartItem.objects.get_or_create( @@ -632,6 +659,10 @@ def add_to_cart(request: HttpRequest, product_id: int): }) return redirect('view_cart') + + except ValueError: + messages.error(request, "Cantidad no válida.") + return redirect('producto', id=product_id) except Product.DoesNotExist: messages.error(request, "Producto no encontrado.") @@ -641,9 +672,13 @@ def add_to_cart(request: HttpRequest, product_id: int): def view_cart(request: HttpRequest): """Muestra el contenido del carrito""" cart = get_or_create_cart(request) + cart_items = list(cart.items.select_related("product")) + active_reservation_ids = _get_active_reservation_ids_for_request(request) + stock_issues = _get_cart_stock_issues(cart_items, exclude_reservation_ids=active_reservation_ids) return render(request, "tienda/cart.html", { "cart": cart, - "cart_items": cart.items.all() + "cart_items": cart_items, + "stock_issues": stock_issues, }) @@ -652,10 +687,21 @@ def update_cart_item(request: HttpRequest, item_id: int): try: cart = get_or_create_cart(request) cart_item = CartItem.objects.get(id=item_id, cart=cart) + + _cancel_active_stock_reservations_for_request(request) + _clear_stock_reservation_session(request) quantity = int(request.POST.get('quantity', 1)) if quantity > 0: + available = _get_available_stock_by_product([cart_item.product_id]).get(cart_item.product_id, 0) + if quantity > available: + messages.error( + request, + f"No hay stock suficiente de '{cart_item.product.name}'. Disponible: {available}, solicitado: {quantity}.", + ) + return redirect('view_cart') + cart_item.quantity = quantity cart_item.save() messages.success(request, "Cantidad actualizada.") @@ -668,12 +714,17 @@ def update_cart_item(request: HttpRequest, item_id: int): except CartItem.DoesNotExist: messages.error(request, "Producto no encontrado en el carrito.") return redirect('view_cart') + except ValueError: + messages.error(request, "Cantidad no válida.") + return redirect('view_cart') def remove_from_cart(request: HttpRequest, item_id: int): """Elimina un producto del carrito""" try: cart = get_or_create_cart(request) + _cancel_active_stock_reservations_for_request(request) + _clear_stock_reservation_session(request) cart_item = CartItem.objects.get(id=item_id, cart=cart) product_name = cart_item.product.name cart_item.delete() @@ -688,6 +739,8 @@ def remove_from_cart(request: HttpRequest, item_id: int): def clear_cart(request: HttpRequest): """Vacía todo el carrito""" cart = get_or_create_cart(request) + _cancel_active_stock_reservations_for_request(request) + _clear_stock_reservation_session(request) cart.items.all().delete() messages.success(request, "Carrito vaciado.") return redirect('view_cart') @@ -769,12 +822,13 @@ def crear_producto(request: HttpRequest): briefdesc = request.POST.get("briefdesc") description = request.POST.get("description") price = request.POST.get("price") + stock = request.POST.get("stock") category_id = request.POST.get("category") primary_image_file = request.FILES.get("primary_image") secondary_images_files = request.FILES.getlist("secondary_images") # Validaciones - if not all([name, description, price, category_id]): + if not all([name, description, price, stock, category_id]): messages.error(request, "Por favor completa todos los campos obligatorios.") categories = Category.objects.all() return render(request, "tienda/crear_producto.html", {"categories": categories}) @@ -787,6 +841,15 @@ def crear_producto(request: HttpRequest): messages.error(request, "El precio debe ser un número válido.") categories = Category.objects.all() return render(request, "tienda/crear_producto.html", {"categories": categories}) + + try: + stock = int(stock) + if stock < 0: + raise ValueError("El stock no puede ser negativo") + except ValueError: + messages.error(request, "El stock debe ser un número entero válido.") + categories = Category.objects.all() + return render(request, "tienda/crear_producto.html", {"categories": categories}) try: category = Category.objects.get(id=category_id) @@ -809,6 +872,7 @@ def crear_producto(request: HttpRequest): briefdesc=briefdesc or "", description=description, price=price, + stock=stock, category=category, primary_image=primary_image, creator=request.user @@ -841,11 +905,12 @@ def editar_producto(request: HttpRequest, id: int): briefdesc = request.POST.get("briefdesc") description = request.POST.get("description") price = request.POST.get("price") + stock = request.POST.get("stock") category_id = request.POST.get("category") primary_image_file = request.FILES.get("primary_image") secondary_images_files = request.FILES.getlist("secondary_images") - if not all([name, description, price, category_id]): + if not all([name, description, price, stock, category_id]): messages.error(request, "Por favor completa todos los campos obligatorios.") categories = Category.objects.all() return render(request, "tienda/editar_producto.html", { @@ -865,6 +930,18 @@ def editar_producto(request: HttpRequest, id: int): "producto": producto }) + try: + stock = int(stock) + if stock < 0: + raise ValueError("El stock no puede ser negativo") + except ValueError: + messages.error(request, "El stock debe ser un número entero válido.") + categories = Category.objects.all() + return render(request, "tienda/editar_producto.html", { + "categories": categories, + "producto": producto + }) + try: category = Category.objects.get(id=category_id) except Category.DoesNotExist: @@ -879,6 +956,7 @@ def editar_producto(request: HttpRequest, id: int): producto.briefdesc = briefdesc or "" producto.description = description producto.price = price + producto.stock = stock producto.category = category if primary_image_file: @@ -925,12 +1003,16 @@ def borrar_producto(request: HttpRequest, id: int): @login_required def checkout(request: HttpRequest): cart = get_or_create_cart(request) - cart_items = cart.items.select_related("product") + cart_items = list(cart.items.select_related("product")) + active_reservation_ids = _get_active_reservation_ids_for_request(request) + stock_issues = _get_cart_stock_issues(cart_items, exclude_reservation_ids=active_reservation_ids) addresses = ShippingAddress.objects.filter(user=request.user) return render(request, "tienda/checkout.html", { "cart": cart, "cart_items": cart_items, "addresses": addresses, + "stock_issues": stock_issues, + "reservation_minutes": STOCK_RESERVATION_MINUTES, }) @csrf_exempt @@ -954,11 +1036,24 @@ def create_checkout_session(request: HttpRequest): return JsonResponse({"error": "Debes seleccionar una dirección de envío válida."}, status=400) cart = get_or_create_cart(request) - cart_items = cart.items.select_related("product") + cart_items = list(cart.items.select_related("product")) - if not cart_items.exists(): + if not cart_items: return JsonResponse({"error": "El carrito está vacío"}, status=400) + active_reservation_ids = _get_active_reservation_ids_for_request(request) + stock_issues = _get_cart_stock_issues(cart_items, exclude_reservation_ids=active_reservation_ids) + if stock_issues: + return JsonResponse({"error": _build_stock_issue_message(stock_issues[0])}, status=400) + + reservation, reservation_issues = _create_stock_reservation_for_cart( + request, + cart_items, + StockReservation.PAYMENT_STRIPE, + ) + if reservation is None: + return JsonResponse({"error": reservation_issues[0]}, status=400) + stripe.api_key = settings.STRIPE_SECRET_KEY line_items = [] @@ -995,6 +1090,8 @@ def create_checkout_session(request: HttpRequest): request.session['stripe_session_id'] = session.id request.session['selected_shipping_address_id'] = shipping_address.id + request.session[STOCK_RESERVATION_SESSION_KEY] = reservation.id + request.session[STOCK_RESERVATION_PAYMENT_SESSION_KEY] = StockReservation.PAYMENT_STRIPE return JsonResponse({"sessionId": session.id}) except Exception as e: @@ -1002,20 +1099,37 @@ def create_checkout_session(request: HttpRequest): return JsonResponse({"error": f"Error al crear sesión de pago: {str(e)}"}, status=500) +@login_required def checkout_success(request: HttpRequest): payment_reference = request.session.get('stripe_session_id', "") shipping_address_id = request.session.get('selected_shipping_address_id') shipping_address = ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() - order = create_order_from_cart(request, Order.PAYMENT_STRIPE, payment_reference, shipping_address) + reservation = _get_session_stock_reservation(request, StockReservation.PAYMENT_STRIPE) + order, order_error = create_order_from_cart( + request, + Order.PAYMENT_STRIPE, + payment_reference, + shipping_address, + stock_reservation=reservation, + ) + + if order is None: + messages.error(request, order_error) + return redirect("checkout") + if 'stripe_session_id' in request.session: del request.session['stripe_session_id'] if 'selected_shipping_address_id' in request.session: del request.session['selected_shipping_address_id'] + _clear_stock_reservation_session(request) messages.success(request, "Pago realizado correctamente. ¡Gracias por tu compra!") return render(request, "tienda/checkout_success.html", {"order": order}) +@login_required def checkout_cancel(request: HttpRequest): + _cancel_active_stock_reservations_for_request(request) + _clear_stock_reservation_session(request) messages.info(request, "Pago cancelado. Puedes intentarlo de nuevo cuando quieras.") return render(request, "tienda/checkout_cancel.html", {}) @@ -1057,11 +1171,24 @@ def create_paypal_payment(request: HttpRequest): import paypalrestsdk cart = get_or_create_cart(request) - cart_items = cart.items.select_related("product") + cart_items = list(cart.items.select_related("product")) - if not cart_items.exists(): + if not cart_items: return JsonResponse({"error": "El carrito está vacío"}, status=400) + active_reservation_ids = _get_active_reservation_ids_for_request(request) + stock_issues = _get_cart_stock_issues(cart_items, exclude_reservation_ids=active_reservation_ids) + if stock_issues: + return JsonResponse({"error": _build_stock_issue_message(stock_issues[0])}, status=400) + + reservation, reservation_issues = _create_stock_reservation_for_cart( + request, + cart_items, + StockReservation.PAYMENT_PAYPAL, + ) + if reservation is None: + return JsonResponse({"error": reservation_issues[0]}, status=400) + # Configurar PayPal paypalrestsdk.configure({ "mode": settings.PAYPAL_MODE, @@ -1124,6 +1251,8 @@ def create_paypal_payment(request: HttpRequest): # Guardar el payment ID en sesión request.session['paypal_payment_id'] = payment.id request.session['selected_shipping_address_id'] = shipping_address.id + request.session[STOCK_RESERVATION_SESSION_KEY] = reservation.id + request.session[STOCK_RESERVATION_PAYMENT_SESSION_KEY] = StockReservation.PAYMENT_PAYPAL # Encontrar el link de aprobación for link in payment.links: @@ -1178,13 +1307,25 @@ def paypal_execute(request: HttpRequest): # Pago exitoso - crear pedido y limpiar el carrito shipping_address_id = request.session.get('selected_shipping_address_id') shipping_address = ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() - order = create_order_from_cart(request, Order.PAYMENT_PAYPAL, payment_id, shipping_address) + reservation = _get_session_stock_reservation(request, StockReservation.PAYMENT_PAYPAL) + order, order_error = create_order_from_cart( + request, + Order.PAYMENT_PAYPAL, + payment_id, + shipping_address, + stock_reservation=reservation, + ) + + if order is None: + messages.error(request, order_error) + return redirect("checkout") # Limpiar la sesión if 'paypal_payment_id' in request.session: del request.session['paypal_payment_id'] if 'selected_shipping_address_id' in request.session: del request.session['selected_shipping_address_id'] + _clear_stock_reservation_session(request) messages.success(request, "¡Pago realizado correctamente con PayPal! Gracias por tu compra.") return render(request, "tienda/checkout_success.html", {"order": order}) @@ -1197,6 +1338,8 @@ def paypal_execute(request: HttpRequest): logger.exception("PAYPAL_EXECUTE_EXCEPTION user_id=%s error=%s", request.user.id, str(e)) messages.error(request, f"Error: {str(e)}") return redirect("checkout") + + def search_suggestions(request: HttpRequest): """API AJAX que retorna sugerencias de búsqueda en JSON""" query = request.GET.get('q', '').strip()