ó
    õhÉj…0  ã                   óª  • S r SSKrSSKrSSKrSSKJr  SSKJr  SSKrSSK	J
r
  SSKJr  SSKJrJr  SSKJr  \R$                  " \5      r\R*                  " \
R,                  5      rS	SS
S.rSSSSS.rSSSSSSSSSSSS.rS
\S\\S.rSSS .S!S"S#S$S.S%S&S&S'S(S)S*S+S,.S-.r1 S.k1 S/k1 S0kS1.rS2rS3\S4\4S5 jr S6\\!   S7\!4S8 jr"S9\S7\!4S: jr#S9\S6\!S7\!4S; jr$S<\!S7\!4S= jr%S<\!S7\4S> jr&S?\S<\!S7\'\\4   4S@ jr(SA\SB\SC\S7S4SD jr)S<\!S7\!4SE jr*SF\+SG\SH\S<\!S7\+4
SI jr,SPS?\SH\S<\!S9\S7\4
SJ jjr-SK\SL\SM\.S7S4SN jr/SQS?\S9\SM\.S<\\!   S7\\   4
SO jjr0g)Ru÷  
protection_service.py â€” protecciÃ³n de caras en servidor (port de hec-photo-app).

Dos ejes independientes y combinables, como en HEC (ambos apagables por separado):

  - `protection_heads_enabled`: el archivo que SE SERVIRÃ� lleva las caras difuminadas, horneadas en
    servidor con StoryFace KIS (`POST /api/v2/kis/apply_effect`, 1 crÃ©dito por llamada).
  - `protection_overlay` (`none` | `lens` | `puzzle`): capa animada en el NAVEGADOR sobre lo que se
    sirva, que estropea las capturas de pantalla. AquÃ­ solo se guarda y valida su configuraciÃ³n; la
    dibujan los componentes de `frontend/src/components/protection/`.

No se precalcula nada al ingerir la foto: proteger por adelantado CADA foto de CADA familia gastarÃ­a un
crÃ©dito aunque esa familia no abra nunca el portal. Se protege bajo demanda, la primera vez que se pide
la vista previa, y el resultado queda en disco (`.protect/` junto al original; las visitas siguientes
son gratis e instantÃ¡neas).

âš ï¸� Diferencia deliberada con HEC: allÃ­, si KIS falla, esa foto se sirve SIN proteger (fail-open, para no
bloquear la venta). AquÃ­ el respaldo es la vista previa reducida con marca de agua (`media_service`):
la familia sigue viendo y comprando la foto, pero nunca un original sin proteger.
é    N)ÚPath)ÚOptional)Úconfig)Úget_db)Ú	ai_healthÚphoto_service)Úwith_retriesç      ð?F)Ú	intensityÚ	max_headsÚinverté   éZ   g      @ç       @)ÚnumBallsÚradiusÚzoomÚspeedÚmixÚclassicé   é   é   gÍÌÌÌÌÌì?gÍÌÌÌÌÌÜ?é
   Té*   )ÚshapeÚ	cut_styleÚ
num_piecesÚmissingÚswapsÚcircle_sizeÚ	mix_ratioÚborder_widthÚanimateÚseedÚgapÚnone©Úprotection_heads_enabledÚprotection_headsÚprotection_overlayÚprotection_lensÚprotection_puzzle)ç        r   )r   é2   )r   r   )r   r   )é   é    )r
   ç      @)g      à?r2   )r   é   )r   r   )g333333ã?r
   )r.   r
   )r   é   )r   é   )r   i@B )r   r   r    r!   r"   r#   r&   r%   )ÚheadsÚlensÚpuzzle>   r7   r'   r8   >   r   Úcircler8   >   ÚwavyÚsharpr   Ústraight)Úoverlayr   r   Úportal_protectionÚsectionÚkeyc           	      óJ  • [         U    U   u  pE [        U5      nXFs=::  a  U::  d  O  [        U  SU SU SU 35      e[	        U[
        5      (       a   [	        U[        5      (       d  [        U5      $ [        US5      $ ! [        [        4 a    [        U  SU S35      ef = f)NÚ.z: invalid numberz: debe estar entre z y r   )Ú_RANGESÚfloatÚ	TypeErrorÚ
ValueErrorÚ
isinstanceÚintÚboolÚround)r?   r@   ÚvalueÚdefaultÚloÚhiÚnums          Ú^D:\Ingenieria\TRABAJOS ONLINE\Projects\11- Photo School\backend\services\protection_service.pyÚ_numberrQ   E   s¯   € Ü�WÑ˜cÑ"�F€Bð=Ü�E‹lˆð �?˜�?Ü˜G˜9 A c UÐ*=¸b¸TÀÀRÀDÐIÓJÐJÜ! '¬3×/Ñ/¼
À7ÌD×8QÑ8QŒ3ˆs‹8ÐdÔW\Ð]`ÐbcÓWdÐdøô	 ”zÐ"ó =Ü˜G˜9 A c UÐ*:Ð;Ó<Ð<ð=ús   �B  Â "B"ÚrawÚreturnc                 óà  • U =(       d    0 n [        U R                  SS5      5      [        [        5      U R                  SS5      [        [        5      [        [
        5      S.nUS   [        S   ;  a  [        S5      eU R                  S5      =(       d    0 R                  5        HG  u  p#US	:X  a  [        U5      US   S	'   M  U[        S
   ;   d  M-  [        S
X#[        U   5      US   U'   MI     U R                  S5      =(       d    0 R                  5        H.  u  p#U[        S   ;   d  M  [        SX#[        U   5      US   U'   M0     U R                  S5      =(       d    0 R                  5        Hv  u  p#U[        ;   a%  U[        U   ;  a  [        SU S35      eX1S   U'   M4  US:X  a  [        U5      US   S'   MM  U[        S   ;   d  M\  [        SX#[
        U   5      US   U'   Mx     U$ )u[   Mezcla `raw` con los valores por defecto y valida rangos. ValueError si algo no es vÃ¡lido.r)   Fr+   r'   r(   r=   z'protection_overlay: none, lens o puzzler*   r   r6   r,   r7   r-   zpuzzle.z: invalid valuer$   r8   )rI   ÚgetÚdictÚDEFAULT_HEADS_CONFIGÚDEFAULT_LENS_CONFIGÚDEFAULT_PUZZLE_CONFIGÚ_ENUMSrF   ÚitemsrC   rQ   )rR   ÚoutÚkÚvs       rP   Únormalize_configr_   P   sÙ  € à
�)�€Cä$(¨¯©Ð1KÈUÓ)SÓ$TÜ Ô!5Ó6Ø!Ÿg™gÐ&:¸FÓCÜÔ 3Ó4Ü!Ô"7Ó8ñ€Cð ÐÑ ¬¨yÑ(9Ó9ÜÐBÓCÐCØ—‘Ð+Ó,×2°×9Ñ9Ö;‰ˆØ�‹=Ü04°Q³ˆCÐ"Ñ# HÓ-Ø”'˜'Ñ"Õ"Ü)0°¸!Ô@TÐUVÑ@WÓ)XˆCÐ"Ñ# AÓ&ñ	 <ð
 —‘Ð*Ó+×1¨r×8Ñ8Ö:‰ˆØ”˜‘ÕÜ(/°¸Ô>QÐRSÑ>TÓ(UˆCÐ!Ñ" 1Ó%ñ ;ð —‘Ð,Ó-×3°×:Ñ:Ö<‰ˆØ”‹;Øœ˜q™	Ó!Ü  7¨1¨#¨_Ð!=Ó>Ð>Ø*+Ð#Ñ$ QÓ'Ø�)‹^Ü26°q³'ˆCÐ#Ñ$ YÓ/Ø”'˜(Ñ#Õ#Ü*1°(¸AÔBWÐXYÑBZÓ*[ˆCÐ#Ñ$ QÓ'ñ =ð €Jó    Ú	school_idc                 óx  • SS K n[        5        nUR                  SU [        45      R	                  5       nS S S 5         [        W(       a"  US   (       a  UR                  US   5      5      $ 0 5      $ ! , (       d  f       NC= f! [         a+  n[        R                  SX5        [        0 5      s S nA$ S nAff = f)Nr   z=SELECT value FROM school_settings WHERE school_id=? AND key=?rK   uP   [%s] ConfiguraciÃ³n de protecciÃ³n ilegible, se usan los valores por defecto: %s)
Újsonr   ÚexecuteÚ
_STORE_KEYÚfetchoner_   ÚloadsÚ	ExceptionÚloggerÚwarning)ra   rc   ÚconnÚrowÚexcs        rP   Úget_protectionrn   p   s”   € ÛÜ	Œ�TØ�l‰lÐZÐ]fÔhrÐ\sÓt×}Ñ}Óˆ÷ 
ð$Ü¾CÀCÈÇL §
¡
¨3¨w©<Ó 8ÓYÐYÐVXÓYÐY÷ 
�ûô ó $Ü�‰ÐiÐktÔzÜ Ó#Õ#ûð$ús.   �'A3¿-B Á-B Á3
BÂ
B9Â B4Â.B9Â4B9c           	      óÞ   • SS K n[        U5      n[        5        nUR                  SU [        UR                  U5      45        UR                  5         S S S 5        U$ ! , (       d  f       U$ = f)Nr   zMINSERT OR REPLACE INTO school_settings (school_id, key, value) VALUES (?,?,?))rc   r_   r   rd   re   ÚdumpsÚcommit)ra   rR   rc   Úcfgrk   s        rP   Úset_protectionrs   {   s[   € ÛÜ
˜3Ó
€CÜ	Œ�TØ�‰ÐdØ¤¨T¯Z©Z¸«_Ð=ô	?à�‰Œ÷ 
ð €J÷	 
Œð €Jús   š9AÁ
A,rr   c                 ó(   • U S   U S   U S   U S   S.$ )ud   Lo que ve el navegador del portal: el overlay y su configuraciÃ³n. Lo de caras es cosa del servidor.r+   r,   r-   r)   )r=   r7   r8   r6   © ©rr   s    rP   Úpublic_viewrw   …   s2   € ð Ð+Ñ,ØÐ%Ñ&ØÐ)Ñ*ØÐ/Ñ0ñ	ð r`   c           	      ó\  • [        [        U R                  S[        S   5      5      S5      [	        U R                  S[        S   5      5      [        U R                  S[        S   5      5      4n[        R                  " [        U5      R                  5       5      R                  5       SS $ )uf   Huella de los parÃ¡metros que cambian el resultado visual (normaliza tipos: `1` y `1.0` son lo mismo).r   é   r   r   Nr   )rJ   rD   rU   rW   rH   rI   ÚhashlibÚsha1ÚreprÚencodeÚ	hexdigest)rr   r@   s     rP   Úconfig_fingerprintr   ‘   s‹   € ô 	Œe�C—G‘G˜KÔ)=¸kÑ)JÓKÓLÈaÓPÜˆC�G‰G�KÔ!5°kÑ!BÓCÓDÜˆS�W‰W�XÔ3°HÑ=Ó>Ó?ð€Cô
 �<Š<œ˜S›	×(Ñ(Ó*Ó+×5Ñ5Ó7¸¸Ð<Ð<r`   Ú
photo_pathc                 ó^   • U R                   S-  nX"U R                   S[        U5       S3-  4$ )uÆ   (carpeta de cachÃ©, archivo protegido). El nombre lleva la huella de `cfg` a propÃ³sito: sin ella,
cambiar los ajustes no tenÃ­a efecto, porque se servÃ­a el archivo cacheado con el ajuste ANTERIOR.z.protectÚ_heads_z.jpg)ÚparentÚstemr   )r€   rr   Úprotect_dirs      rP   Úprotect_pathsr†   ›   s=   € ð ×#Ñ# jÑ0€KØ¨¯©Ð(9¸ÔASÐTWÓAXÐ@YÐY]Ð&^Ñ^Ð^Ð^r`   r…   r„   Úkeepc                 óÂ   •  U R                  U S35       H  nX2:w  d  M
  UR                  SS9  M     g! [         a   n[        R	                  SX5         SnAgSnAff = f)zSBorra las variantes cacheadas de OTRAS configuraciones. Best-effort: nunca propaga.z_heads_*T)Ú
missing_oku3   heads: no se pudo limpiar la cachÃ© vieja de %s: %sN)ÚglobÚunlinkrh   ri   rj   )r…   r„   r‡   Ústalerm   s        rP   Ú_forget_stale_variantsr�   ¢   s]   € ðYØ ×%Ñ%¨¨¨hÐ&7Ö8ˆEØ�}Ø—‘¨�Ó-ò 9øô ó YÜ�‰ÐLÈd×XÑXûðYús   ‚4  4 ´
A¾AÁAc           	      óÄ   • S[        U R                  S[        S   5      5      [        U R                  S[        S   5      5      U R                  S5      (       a  SS.$ SS.$ )u»  Campos del formulario de `apply_effect`.

âš ï¸� El `protect_invert` de KIS estÃ¡ al revÃ©s de lo que promete nuestro interruptor: con `false` (su
valor por defecto) el efecto se aplica AL FONDO y la cara queda nÃ­tida; con `true` se aplica A LA
CARA. Nuestro `invert` significa lo contrario (sin marcar: protege las caras), asÃ­ que se manda
invertido. Bug real en producciÃ³n de HEC: sin esto el interruptor hacÃ­a lo opuesto a su etiqueta.
Úblurr   r   r   ÚfalseÚtrue)Úprotect_effectÚprotect_intensityÚprotect_max_headsÚprotect_invert)ÚstrrU   rW   rv   s    rP   Úkis_form_datar—   ¬   sd   € ð !Ü  §¡¨Ô6JÈ;Ñ6WÓ!XÓYÜ  §¡¨Ô6JÈ;Ñ6WÓ!XÓYØ%(§W¡W¨X×%6Ñ%6˜'ñ	ð ð =Cñ	ð r`   Úphoto_bytesÚfilenameÚ	api_tokenc           	   ƒ   óÈ  #   • [         R                  " SS9 IS h  v•N nUR                  [        R                  SXS40[        U5      SU0S9I S h  v•N nUR                  S:w  aV  [        R                  SUR                  UR                  S S	 5        [        S
UR                   SUR                  S S  35      eUR                  5       R                  S5      nU(       d  [        S5      eUR                  U5      I S h  v•N nUR                  S:w  a  [        S5      eUR                  sS S S 5      IS h  v•N   $  GN$ Nï NA N! , IS h  v•N  (       d  f       g = f7f)Ng      ^@)ÚtimeoutÚimagez
image/jpegz	X-API-Key)ÚfilesÚdataÚheaderséÈ   zKIS apply_effect error %s: %siô  zKIS apply_effect failed (HTTP z): Úurlz#KIS apply_effect: respuesta sin urlz1KIS apply_effect: error al descargar el resultado)ÚhttpxÚAsyncClientÚpostr   ÚKIS_API_URLr—   Ústatus_coderi   ÚerrorÚtextrh   rc   rU   Úcontent)r˜   r™   rš   rr   ÚclientÚresponser¢   Údownloads           rP   Ú	_call_kisr®   ¼   s:  é € Ü× Ò ¨×/Ò/°6ØŸ™Ü×ÑØ˜X°LÐAÐBÜ˜sÓ#Ø  )Ð,ð	 %ð 
÷ 
ˆð ×Ñ 3Ó&Ü�L‰LÐ8¸(×:NÑ:NÐPX×P]ÑP]Ð^bÐ_bÐPcÔdÜÐ<¸X×=QÑ=QÐ<RÐRUÐV^×VcÑVcÐdhÐehÐViÐUjÐkÓlÐlØ�m‰m‹o×!Ñ! %Ó(ˆÞÜÐAÓBÐBØŸ™ C›×(ˆØ×Ñ 3Ó&ÜÐOÓPÐPØ×Ñ÷! 0×/Ô/ñ
ñ )÷ 0×/×/Ð/üsi   ‚E"›D?œE"Ÿ3EÁEÁB/EÄEÄ*EÄ-E"Ä9EÄ:E"ÅEÅEÅE"ÅEÅEÅEÅE"c              ƒ   óÆ  ^ ^^^	#   • [        T T5      u  pEUR                  5       (       a  U$ [         ISh  v•N   UR                  5       (       a  UsSSS5      ISh  v•N   $ UR                  SS9  [        R
                  " T R                  5      I Sh  v•N m	 [        UUU	U 4S jST R                   3S9I Sh  v•N n[        R                  " U5        UR                  S5      n[        R
                  " UR                  U5      I Sh  v•N   [        R
                  " UR                   U5      I Sh  v•N   [        R
                  " ["        UT R$                  U5      I Sh  v•N   SSS5      ISh  v•N   U$  GNE GN! Në NÆ! [         a,  n[        R                  " U[        R                  U5        e SnAff = f N± NŠ N] NO! , ISh  v•N  (       d  f       U$ = f7f)u¨   VersiÃ³n con caras protegidas de una foto, cacheada en disco. Idempotente por foto Y por configuraciÃ³n.
Propaga la excepciÃ³n si falla: el llamante decide quÃ© servir.NT)Úexist_okc                  ó4   >• [        TTR                  T T5      $ ©N)r®   Úname)rš   rr   r˜   r€   s   €€€€rP   Ú<lambda>Úensure_heads.<locals>.<lambda>Ü   s   ø€ ¬Y°{ÀJÇOÁOÐU^Ð`cÔ-dr`   zkis-protect )Úlabelú.tmp)r†   ÚexistsÚ_semÚmkdirÚasyncioÚ	to_threadÚ
read_bytesr	   r³   rh   r   Úrecord_failureÚ
TASK_HEADSÚrecord_successÚwith_suffixÚwrite_bytesÚreplacer�   r„   )
r€   rš   rr   ra   r…   r\   rR   rm   Útmpr˜   s
   ```      @rP   Úensure_headsrÅ   Ð   sn  ûé € ô % Z°Ó5Ñ€KØ
‡z�z‡|�|Øˆ
ßŽtØ�:‰:�<‰<Ø÷ �t‰tð 	×Ñ 4ÐÑ(Ü#×-Ò-¨j×.CÑ.CÓD×Dˆð	Ü$×%dØ-9¸*¿/¹/Ð9JÐ+KñM÷ MˆCô
 	× Ò  Ô+Ø�o‰o˜fÓ%ˆÜ×Ò §¡°Ó5×5Ð5Ü×Ò §¡¨SÓ1×1Ð1Ü×ÒÔ 6¸ÀZÇ_Á_ÐVYÓZ×ZÐZ÷ �tð  €Jõ! ñ EñMøäó 	Ü×$Ò$ Y´	×0DÑ0DÀcÔJØûð	úñ
 	6Ù1ÙZ÷ �t�tð  €JüsÛ   †0G!¶E;·G!ºGÁG!ÁE>ÁG!Á#2GÂFÂGÂ!FÂ<FÂ=FÃAGÄF>Ä(GÄ5G Ä6.GÅ$GÅ%GÅ)G!Å4GÅ5G!Å>G!ÆGÆFÆ
F;Æ'F6Æ6F;Æ;GÇ GÇGÇG!ÇGÇGÇGÇG!ÚsrcÚdstÚmax_pxc                 ó¤  • SSK JnJn  UR                  R	                  SSS9  UR                  U 5       nUR                  U5      nUR                  X"4UR                  5        UR                  S;  a  UR                  S5      nUR                  S5      nUR                  USS	S
S9  UR                  U5        S S S 5        g ! , (       d  f       g = f)Nr   )ÚImageÚImageOpsT)Úparentsr°   )ÚRGBÚRGBArÍ   r·   ÚWEBPéP   r   )ÚformatÚqualityÚmethod)ÚPILrÊ   rË   rƒ   rº   ÚopenÚexif_transposeÚ	thumbnailÚLANCZOSÚmodeÚconvertrÁ   ÚsaverÃ   )rÆ   rÇ   rÈ   rÊ   rË   ÚimgrÄ   s          rP   Ú_resize_webprÝ   é   s    € ß#Ø‡J�J×Ñ˜T¨DÐÑ1Ø	�‰�CŒ˜CØ×%Ñ% cÓ*ˆØ�‰�vÐ&¨¯©Ô6Ø�8‰8˜?Ó*Ø—+‘+˜eÓ$ˆCØ�o‰o˜fÓ%ˆØ�‰�˜V¨R¸ˆÑ:Ø�‰�CÔ÷ 
�Žús   ´BCÃ
Cc              ƒ   óþ  #   • U=(       d    [        U5      nUS   (       a  [        R                  (       d  gUS   n[        U [        R                  XA5      I Sh  v•N n[	        U5      n[
        R                  " U SU SU S35      nUR                  5       (       a6  UR                  5       R                  UR                  5       R                  :  a$  [        R                  " [        XWU5      I Sh  v•N   U$  Nž N7f)už   Vista previa de la foto con las caras protegidas, o None si la protecciÃ³n estÃ¡ apagada / sin clave.
Lanza si KIS falla (el llamante cae a la marca de agua).r)   Nr*   z.hrB   z.webp)rn   r   ÚSTORYFACE_API_KEYrÅ   r   r   Úmirror_pathr¸   ÚstatÚst_mtimer»   r¼   rÝ   )r€   ra   rÈ   rr   Ú	heads_cfgÚkis_outÚfprÇ   s           rP   Úprotected_previewræ   ö   sÊ   é € ð ×
*” 	Ó*€CØÐ)×*´&×2J×2JØØÐ&Ñ'€IÜ  ¬V×-EÑ-EÀyÓ\×\€GÜ	˜IÓ	&€BÜ
×
#Ò
# J°"°R°D¸¸&¸ÀÐ0GÓ
H€CØ�:‰:�<‰<˜3Ÿ8™8›:×.Ñ.°·±³×1HÑ1HÓHÜ×Ò¤¨g¸FÓC×CÐCØ€Jñ ]ñ 	Dùs%   ‚AC=ÁC9ÁBC=Ã2C;Ã3C=Ã;C=)Ú r²   )1Ú__doc__r»   rz   ÚloggingÚpathlibr   Útypingr   r£   Úcorer   Úcore.dbr   Úservicesr   r   Úutils.retryr	   Ú	getLoggerÚ__name__ri   Ú	SemaphoreÚKIS_PARALLEL_MAXr¹   rW   rX   rY   ÚDEFAULT_PROTECTIONrC   rZ   re   r–   rQ   rV   r_   rn   rs   rw   r   Útupler†   r�   r—   Úbytesr®   rÅ   rH   rÝ   ræ   ru   r`   rP   Ú<module>r÷      s‚  ðñó( Û Û Ý Ý ã å Ý ß -Ý $à	×	Ò	˜8Ó	$€à×Ò˜×0Ñ0Ó1€à%(°qÀEÑJÐ Ø#$°¸CÈ#ÑNÐ à ¸!ÈÐTUÐfjØ r°dÀBÈqñÐ ð
 !&Ø,Ø Ø*Ø.ñÐ ð &°GÑ<Ø ¨I¸zÐT^Ñ_Ø$°À&ÐYcØ&¸ÈÐYgñiñ€ò *Ú(Ú9ñ
€ð
 !€
ð
e�Sð e˜sô eð˜( 4™.ð ¨Tô ð@$˜cð $ dô $ð˜cð ¨ð °ô ð�Tð ˜dô ð=˜Dð = Sô =ð_˜dð _¨ð _°%¸¸d¸
Ñ2Cô _ðY¨ð Y°Cð Y¸tð YÈô Yð�tð  ô ð   ð  °#ð  À#ð  ÈDð  ÐUZô  ñ( 4ð °Cð ¸dð Èsð Ð\`õ ð2
�dð 
 ð 
¨sð 
°tô 
ñ¨ð ¸ð Àcð ÐPXÐY]ÑP^ð ÐjrÐswÑjxö r`   