ó
    > Ìj­d  ã            	       ó  • % S r SSKrSSKrSSKrSSKrSSKrSSKrSSKJrJ	r	  SSK
J
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  SS
KJr  SSKJrJr  SSKJrJrJrJr  \R@                  " \!5      r"Sr#\$" S\%" S5      RM                  5       =(       d    S5      r'\RP                  r(\( V s1 s H  o RS                  5       iM     sn r*S1r+Sr,Sr-S\.S\4S jr/0 r0\1\.\14   \2S'   S\14S jr3S\.S\14S jr4S/S\.S\.S\\5   SS4S jjr6S\.SS4S jr7S\.S\\1   4S jr8S\.S\14S jr9S/S\\.   S\14S  jjr:S!\S\;4S" jr<S\5S\54S# jr=S$\S\\
   4S% jr>S/S$\S&S'S\?4S( jjr@ " S) S*5      rA " S+ S,\5      rBS\.SS4S- jrCS0S. jrDgs  sn f )1u  
organizer_service.py â€” ingestiÃ³n de las fotos del fotÃ³grafo (lector de QR), por colegio.

Port de `organizer_service.py` de hec-photo-app. Flujo:

  1. El fotÃ³grafo sube las fotos por FTP a `incoming/{colegio}/`.
  2. El organizer (watchdog + sondeo de seguridad) las analiza en paralelo: QR de la tarjeta de la
     familia (cascada completa en `qr_detect`: puerta doble, CLAHE, Otsu, enfoque, OCR de rescate).
  3. El fotÃ³grafo se deduce del PREFIJO del archivo (`DSCâ€¦` â†’ "xavier"). Cada fotÃ³grafo tiene su propio
     "QR pegajoso" persistido en base: una foto de tarjeta lo fija; las que siguen cuelgan de Ã©l.
  4. Destino: `photos/{colegio}/{fotÃ³grafo}/{CÃ“DIGO}/` (la foto de la tarjeta, en `.qr/`).
     Prefijo desconocido o imagen ilegible â†’ `incoming/â€¦/_errors/`, sin perderla.

âš ï¸� El organizer no debe morir en silencio. Se muriÃ³ de tres maneras en HEC y cada regla de abajo sale
de ese dÃ­a (el test `test_organizer.py` las reproduce):
  - `process_pending` va dentro de `try/finally`: `processing` es su primera guarda, y una excepciÃ³n que
    lo dejaba en True hacÃ­a que TODAS las llamadas siguientes salieran de inmediato.
  - Un archivo malo no tumba el lote: el fallo de un `future` se atrapa por archivo.
  - El `while True` vive FUERA del `try` de cada pasada, con backoff exponencial: una caÃ­da de la base
    retrasa el trabajo, no lo cancela. Solo `CancelledError` (apagado) atraviesa.
  - `_move` devuelve False sin lanzar y se comprueba: si no, la foto seguÃ­a en incoming y el contador
    decÃ­a que estaba organizada.
  - El estado es POR COLEGIO: uno solo compartido dejaba `processing=True` en todos a la vez.
  - watchdog llama desde SU hilo: todo entra al event loop por `call_soon_threadsafe`.
  - El anÃ¡lisis (CPU) y la espera de estabilidad (sleep) van en hilos, no bloquean la API.

Diferencias deliberadas con HEC: sin IA anticipada ni detecciÃ³n de ojos cerrados (en este proyecto la IA
la pide la familia, con cuota), y los fotÃ³grafos/estado se guardan en la misma base que el resto, con otro esquema.
é    N)ÚThreadPoolExecutorÚas_completed)Údatetime)ÚPath)ÚOptional)ÚFileSystemEventHandler)ÚObserver)Úconfig)Úget_db)ÚutcnowÚ
utcnow_iso)Úphoto_serviceÚphotographer_serviceÚ	qr_detectÚschool_serviceTé   Úosé   z.datg     Àr@g      ^@Ú	school_idÚreturnc                 ó  •  [        5        nUR                  S5      R                  5       nSSS5        W(       a  US   U :X  a  [        R                  $ [        R                  U -  $ ! , (       d  f       NA= f! [
         a     N0f = f)u¾   `incoming/{colegio}/`. La instalaciÃ³n anterior (un solo colegio) subÃ­a a la raÃ­z de `incoming/`:
esa carpeta sigue siendo la de ese colegio para no obligar a cambiar el FTP de la cÃ¡mara.z5SELECT value FROM app_kv WHERE key='legacy_school_id'NÚvalue)r   ÚexecuteÚfetchoner
   ÚINCOMING_DIRÚ	Exception)r   ÚconnÚrows      Ú]D:\Ingenieria\TRABAJOS ONLINE\Projects\11- Photo School\backend\services\organizer_service.pyÚincoming_dir_forr    A   ss   € ðÜŒX˜Ø—,‘,ÐVÓW×`Ñ`ÓbˆC÷ æ�3�w‘< 9Ó,Ü×&Ñ&Ð&ô ×Ñ Ñ*Ð*÷ �Xûô ó Ùðús'   ‚
A8 Œ A'¬'A8 Á'
A5Á1A8 Á8
BÂBÚ_status_by_schoolc                  ó"   • SSS SSSS S S S S/ 0 S.$ )NFr   )Ú
processingÚpending_countÚlast_run_atÚlast_processed_countÚlast_elapsed_msÚaliveÚ
started_atÚ
last_ok_atÚ
last_errorÚlast_error_atÚconsecutive_errorsÚfailed_filesÚsticky_by_photographer© r0   ó    r   Ú_new_statusr2   S   s,   € à¨aÀÐ^_Øà d¸$ÈdÐeiØ°Èrñð r1   c                 ó>   • [         R                  U [        5       5      $ ©N)r!   Ú
setdefaultr2   ©r   s    r   Ú_status_forr7   ]   s   € Ü×'Ñ'¨	´;³=ÓAÐAr1   ÚmessageÚfilesc                 óì   • [        U 5      nXS'   [        5       US'   US==   S-  ss'   U(       a4  [        US   5      nUS   U Vs/ s H  oUU;  d  M
  UPM     sn-   SS  US'   [        U 5        g s  snf )Nr+   r,   r-   é   r.   iìÿÿÿ)r7   r   ÚsetÚ_publish_status)r   r8   r9   ÚstÚseenÚfs         r   Ú_record_errorrA   a   s‚   € Ü	�YÓ	€BØ€|ÑÜ$›,€B€ÑØÐÓ Ñ!ÓÞÜ�2�nÑ%Ó&ˆØ  Ñ0¹uÓ3Vºu¸!ÐQUÉ·A¹uÑ3VÑVÐX[ÐX\Ð]ˆˆ>ÑÜ�IÕùò 4Ws   Á	A1ÁA1c                 ó`  •  [        [        U 5      5      n[        5       US'   [        5        nUR	                  SSU  3[
        R                  " U5      45        UR                  5         SSS5        g! , (       d  f       g= f! [         a   n[        R                  SU5         SnAgSnAff = f)zWDeja el estado en base para que cualquier worker lo pueda mostrar (solo uno es leader).Úpublished_atz7INSERT OR REPLACE INTO app_kv (key, value) VALUES (?,?)úorganizer_status:Nz/No se pudo publicar el estado del organizer: %s)Údictr7   r   r   r   ÚjsonÚdumpsÚcommitr   ÚloggerÚdebug)r   Úpayloadr   Úexcs       r   r=   r=   l   sŠ   € ðMÜ”{ 9Ó-Ó.ˆÜ",£,ˆ�ÑÜŒX˜Ø�L‰LÐRØ-¨i¨[Ð9¼4¿:º:ÀgÓ;NÐOôQà�K‰KŒM÷ �XŽXûô ó MÜ�‰ÐFÈ×LÑLûðMús4   ‚+B ­<A2Á)B Á2
B Á<B Â B Â
B-ÂB(Â(B-c                 óæ  •  [        5        nUR                  SSU  345      R                  5       nS S S 5        W(       d  g [        R                  " US   5      n[        5       [        R                  " US   5      -
  R                  5       n[        UR                  S5      5      =(       a     U[        S[        R                  S-  5      :  US'   U$ ! , (       d  f       N§= f! [         a     g f = f)Nz$SELECT value FROM app_kv WHERE key=?rD   r   rC   r(   g     €f@é   )r   r   r   rF   Úloadsr   r   ÚfromisoformatÚtotal_secondsÚboolÚgetÚmaxr
   ÚORGANIZER_POLL_SECSr   )r   r   r   ÚdataÚages        r   Ú_read_publishedrX   y   sË   € ðÜŒX˜Ø—,‘,ÐEÐJ[Ð\eÐ[fÐHgÐGiÓj×sÑsÓuˆC÷ æØÜ�zŠz˜#˜g™,Ó'ˆÜ‹xœ(×0Ò0°°nÑ1EÓFÑF×UÑUÓWˆä˜TŸX™X gÓ.Ó/×d°C¼#¸eÄV×E_ÑE_ÐbcÑEcÓ:dÑ4dˆˆW‰Øˆ÷ �Xûô ó Ùðús.   ‚
C# Œ%C±C# ÁBC# Ã
C ÃC# Ã#
C0Ã/C0c                 óÈ  • [        U 5      nUS   (       d  [        U 5      nU(       a  Un[        U5      n[        UR	                  S/ 5      5      US'   [        [        U 5      5      nXCS'   UR	                  S5      =(       d    UR	                  S5      nSnU(       a0   [        5       [        R                  " U5      -
  R                  5       nUb  [        U5      OSUS'   [        UR	                  S5      (       + =(       dG    US:„  =(       a;    UR	                  S	5      (       + =(       a    USL=(       a    U[        R                  :„  5      US
'   U$ ! [         a    Sn N“f = f)ua   Estado + diagnÃ³stico. `stalled` responde con datos a Â«Â¿hay fotos esperando que nadie mueve?Â».r(   r.   r$   r%   r)   NÚseconds_since_last_runr   r#   Ústalled)r7   rX   rE   ÚlistrS   Ú_count_pendingr    r   r   rP   rQ   Ú
ValueErrorÚintrR   r
   ÚORGANIZER_STALL_SECS)r   r>   Ú	publishedÚoutÚpendingÚ	last_moverW   s          r   Úget_organizer_statusre   ˆ   s2  € ä	�YÓ	€BØˆg�;Ü# IÓ.ˆ	ÞØˆBÜ
ˆr‹(€CÜ˜rŸv™v n°bÓ9Ó:€CˆÑäÔ-¨iÓ8Ó9€GØ"ˆÑØ—‘�}Ó%×=¨¯©°Ó)=€IØ
€CÞð	Ü“8œh×4Ò4°YÓ?Ñ?×NÑNÓPˆCð 14±¤C¨¤HÀT€CÐ Ñ!äØ�G‰G�GÓÔ÷ 	qØ�a‰K×o §¡¨Ó 5Ô5×o¸#ÀT¸/×oÈcÔTZ×ToÑToÑNoó€Cˆ	�Nð €Jøô ó 	ØŠCð	ús   Â/E ÅE!Å E!c                 óx   • U c-  [        [        [        5      S5      n U c  0 [        5       ESSS.E$ [	        U 5      $ )zBAlias para el monitor: sin colegio, el primero que haya arrancado.NF)r[   rZ   )ÚnextÚiterr!   r2   re   r6   s    r   Ú
get_statusri   ¤   s@   € àÑÜœÔ/Ó0°$Ó7ˆ	ØÑØV”k“mÐV°ÐQUÒVÐVÜ 	Ó*Ð*r1   Úincomingc                 ód   •  [        S U R                  5        5       5      $ ! [         a     gf = f)Nc              3   óš   #   • U  HA  oR                  5       (       d  M  UR                  R                  5       [        ;   d  M=  S v •  MC     g7f)r;   N)Úis_fileÚsuffixÚlowerÚ_IMAGE_EXTS_LOWER)Ú.0r@   s     r   Ú	<genexpr>Ú!_count_pending.<locals>.<genexpr>¯   s/   é € ÐjÒ0˜·I±I·K“1ÀAÇHÁHÇNÁNÓDTÔXiÑDi—1‘1Ò0ùs   ‚Až AÁ	Ar   )ÚsumÚiterdirr   )rj   s    r   r]   r]   ­   s3   € ðÜÑj˜h×.Ñ.Ô0ÓjÓjÐjøÜó Ùðús   ‚" ¢
/®/c                 óN  • U (       d  / $ 0 nU  H   n UR                  5       R                  X'   M"     [        R                  " [
        R                  5        / nU  HU  n UR                  5       R                  nXAR                  U5      :w  d  US:X  a  UR                  UR                  5        MU  MW     U(       a*  [        R                  SSR                  USS 5      5        / $ U $ ! [         a    SX'    Mé  f = f! [         a    UR                  UR                  5         MÉ  f = f)uO   Difiere el LOTE ENTERO si algÃºn archivo sigue creciendo (subida FTP en curso).éÿÿÿÿr   u&   Archivos en transferencia (FTP): %sâ€¦ú, NrN   )ÚstatÚst_sizeÚOSErrorÚtimeÚsleepr
   ÚFTP_STABLE_SECSrS   ÚappendÚnamerI   ÚinfoÚjoin)r9   Úsnapshotr@   Ú	uploadingÚsizes        r   Ú_filter_stabler†   ¶   sþ   € æØˆ	Ø "€HÛˆð	ØŸ&™&›(×*Ñ*ˆH‹Kñ ô
 	‡J‚JŒv×%Ñ%Ô&Ø€IÛˆð	%Ø—6‘6“8×#Ñ#ˆDØ—|‘| A“Ó&¨$°!«)Ø× Ñ  §¡Ö(ñ +4ñ ö Ü�‰Ð<¸d¿i¹iÈ	ÐRTÐSTÈÓ>VÔWØˆ	Ø€Løô ó 	ØˆHŒKð	ûô ó 	%Ø×Ñ˜QŸV™V×$ð	%ús$   ’C&ÁAC;Ã&C8Ã7C8Ã;%D$Ä#D$Úpathc                 óÔ  •  SSK Jn  UR                  U 5       nUR                  5       nS nU(       ae  [	        US5      (       a  UR                  S5      O0 nUR                  S5      =(       d)    UR                  S5      =(       d    UR                  S5      nU(       a   [        R                  " US5      sS S S 5        $  S S S 5        g ! , (       d  f       g = f! [         a     g f = f)	Nr   )ÚImageÚget_ifdii‡  i�  i�  i2  z%Y:%m:%d %H:%M:%S)
ÚPILr‰   ÚopenÚgetexifÚhasattrrŠ   rS   r   Ústrptimer   )r‡   r‰   ÚimgÚexifÚrawÚsubs         r   Ú_exif_datetimer”   Ï   s½   € ðÝØ�Z‰Z˜Ô Ø—;‘;“=ˆDØˆCÞÜ.5°d¸I×.FÑ.F�d—l‘l 6Ô*ÈB�Ø—g‘g˜e“n×G¨¯©°«×G¸$¿(¹(À3»-�ÞÜ×(Ò(¨Ð.AÓB÷ Ñð ÷ ð ÷ Ôð ûô ó ØØðús5   ‚C ™BC	Â5	C Ã C Ã	
CÃC ÃC Ã
C'Ã&C'Úprefixeszset[str] | Nonec                 ó6  •  [         R                  " [        U 5      [         R                  5      nUc  U SSS4$ U [        R
                  " X U5      [        U 5      S4$ ! [         a1  n[        R                  SU R                  U5        U SSS4s SnA$ SnAff = f)u½   (ruta, texto QR | None, fecha EXIF | None, ilegible). ResoluciÃ³n completa: a 1/4 fallaban los QR
que ocupan <17 % del encuadre y a 1/2 los <6 %; el volumen (lotes de 1-2 fotos) lo permite.NTFzError analizando %s: %s)Úcv2ÚimreadÚstrÚIMREAD_COLORr   Ú	detect_qrr”   r   rI   Úerrorr€   )r‡   r•   r�   rL   s       r   Ú_analyze_filer�   ß   s‹   € ð'Ü�jŠjœ˜T›¤C×$4Ñ$4Ó5ˆØ‰;Ø˜˜t TÐ)Ð)Ø”Y×(Ò(¨°HÓ=¼~ÈdÓ?SÐUZÐZÐZøÜó 'Ü�‰Ð.°·	±	¸3Ô?Ø�T˜4 Ð&Õ&ûð'ús"   ‚6A ¹#A Á
BÁ'&BÂBÂBc                   óº   • \ rS rSrS\S\S\4S jrSS jrSS
 jrS\	SSS\
\\	4   4S jrSS jrS\S\\   S\S\4S jr\S\S\4S j5       rS\S\S\4S jrSrg	)ÚPhotoOrganizeréî   r   Úincoming_dirÚ
output_dirc                 ó  • Xl         X l        X0l        U R                  S-  U l        U R                  R	                  SSS9  U R                  R	                  SSS9  [
        R                  " 5       U l        S U l        SU l	        g )NÚ_errorsT©ÚparentsÚexist_okç        )
r   rj   ÚoutputÚ	error_dirÚmkdirÚasyncioÚLockÚlockÚ	_prefixesÚ_prefixes_at)Úselfr   r¡   r¢   s       r   Ú__init__ÚPhotoOrganizer.__init__ï   sk   € Ø"ŒØ$ŒØ ŒØŸ™¨Ñ2ˆŒØ�‰×Ñ T°DÐÑ9Ø�‰×Ñ $°ÐÑ6Ü—L’L“NˆŒ	Ø,0ˆŒØˆÕr1   r   úset[str]c           	      ó^  • [         R                  " 5       nU R                  b  XR                  -
  [        :”  a²  S n [
        R                  " U R                  5      R                  S5      =(       d    S n[        R                  " X R                  5      U l        Xl        [        R                  SU R                  SR!                  [#        U R                  5      5      =(       d    S5        U R                  $ ! [         a+  n[        R                  SU R                  U5         S nAN³S nAff = f)NÚqr_code_prefixz'[%s] No se pudo leer qr_code_prefix: %sz$[%s] Prefijos del OCR de rescate: %srx   z	(ninguno))r|   Ú	monotonicr¯   r°   Ú_PREFIX_TTLr   Úget_settingsr   rS   r   rI   Úwarningr   Úvalid_prefixesr©   r�   r‚   Úsorted)r±   ÚnowÚ
configuredrL   s       r   Ú_code_prefixesÚPhotoOrganizer._code_prefixesû   sí   € Ü�nŠnÓˆØ�>‰>Ñ! S×+<Ñ+<Ñ%<¼{Ó%JØˆJð_Ü+×8Ò8¸¿¹ÓH×LÑLÐM]Ó^×fÐbf�
ô '×5Ò5°jÇ+Á+ÓNˆDŒNØ #ÔÜ�K‰KÐ>ÀÇÁÐPT×PYÑPYÔZ`Ðae×aoÑaoÓZpÓPq÷  QAð  vAô  BØ�~‰~Ðøô ó _Ü—‘ÐHÈ$Ï.É.ÐZ]×^Ñ^ûð_ús   ¼8C7 Ã7
D,Ä!D'Ä'D,Nc                 ó>  • [         R                   " 5       n [        U R                  R                  5       5      nU HÔ  n UR                  R                  5       nUR                  5       (       a4  U[        ;   d*  U[        ;   d   UR                  R                  S5      (       a  Mi  XR                  5       R                   -
  ["        R$                  :  a  M™  UR'                  5         [
        R)                  SU R                  UR                  5        MÖ     g! [         a+  n[
        R                  SU R                  U5         SnAgSnAff = f! [*         a     GM  [         a8  n[
        R                  SU R                  UR                  U5         SnAGM[  SnAff = f)z±Quita de incoming lo que no se puede procesar (RAW .NEF, restos de FTP): si no, se acumula para
siempre. Conserva .dat y solo toca ficheros 'asentados' (no una subida en curso).z/[%s] No se pudo leer incoming para limpieza: %sNÚ.z-[%s] No procesable, eliminado de incoming: %sz[%s] No se pudo eliminar %s: %s)r|   r\   rj   ru   r   rI   rœ   r   rn   ro   rm   rp   Ú_PURGE_KEEP_EXTSr€   Ú
startswithry   Úst_mtimer
   ÚPURGE_MIN_AGE_SECSÚunlinkr�   ÚFileNotFoundError)r±   r½   ÚentriesrL   r@   Úexts         r   Ú_purge_unprocessableÚ#PhotoOrganizer._purge_unprocessable  s3  € ô �iŠi‹kˆð	Ü˜4Ÿ=™=×0Ñ0Ó2Ó3ˆGó ˆAð]Ø—h‘h—n‘nÓ&�Ø—y‘y—{‘{ cÔ->Ó&>À#ÔIYÓBYÐ]^×]cÑ]c×]nÑ]nÐor×]sÑ]sÙØŸ™›×*Ñ*Ñ*¬V×-FÑ-FÓFÙØ—‘”
Ü—‘ÐKÈTÏ^É^Ð]^×]cÑ]cÖdò øô ó 	Ü�L‰LÐJÈDÏNÉNÐ\_Ô`Üûð	ûô %ó ÛÜó ]Ü—‘Ð>ÀÇÁÐPQ×PVÑPVÐX[×\Ó\ûð]úsB   —#D Á A#EÂ%.EÃ;EÄ
E
Ä!EÅE
Å
FÅ	FÅ$,FÆFÚ
candidatesr•   c                 óÔ  • 0 n/ n[        [        S9 nU Vs0 s H  oeR                  [        Xb5      U_M     nn[	        U5       H!  nXx   n	 UR                  5       u  p«pÍX¼U4X:'   M#     SSS5        X44$ s  snf ! [         aQ  n[        R                  SU R                  U	R                  USS9  UR                  U	R                  5         SnAM‹  SnAff = f! , (       d  f       X44$ = f)u_   AnÃ¡lisis en paralelo (en un hilo). Un archivo que revienta se queda fuera sin tumbar al resto.)Úmax_workersz[%s] Fallo analizando %s: %sT©Úexc_infoN)r   Ú_WORKERSÚsubmitr�   r   Úresultr   rI   rœ   r   r€   r   )r±   rÍ   r•   ÚcacheÚfailedÚpoolr@   ÚfuturesÚfutÚsrcr‡   ÚqrÚdtÚ
unreadablerL   s                  r   Ú_analyze_batchÚPhotoOrganizer._analyze_batch  sà   € à#%ˆØˆÜ¬HÒ5¸ÙKUÓVÊ:Àa—{‘{¤=°!Ó>ÀÒAÉ:ˆGÐVÜ# GÖ,�Ø‘l�ð,Ø/2¯z©z«|Ñ,�D˜bØ#%¨:Ð"6�E“Kñ	 -÷ 6ð ˆ}Ðùò Wøô !ó ,Ü—L‘LÐ!?ÀÇÁÐQT×QYÑQYÐ[^Ðim�LÑnØ—M‘M #§(¡(×+Ò+ûð,ú÷ 6Ô5ð ˆ}ÐúsF   ’C— A4·CÁA9Á%CÁ4CÁ9
CÂACÃ	CÃCÃCÃ
C'c           
   ƒ   ó˜	  ^#   • [        U R                  5      nUS   (       a  gSUS'   [        R                  " 5       n/ n [        R
                  " U R                  5      I Sh  v•N    U R                  R                  5        Vs/ s Hb  nUR                  5       (       d  M  UR                  R                  5       [        ;   d  M>  UR                  R                  S5      (       a  M`  UPMd     nn[+        U5      US'   U(       dK  [-        5       US	'   S
US'   SUS'    SUS'   ['        U R                  5      US'   [)        U R                  5        g[         R/                  SU R                  [+        U5      U R                  5        [        R
                  " [0        U5      I Sh  v•N nU(       d4   SUS'   ['        U R                  5      US'   [)        U R                  5        g[        R
                  " U R2                  5      I Sh  v•N n[        R
                  " U R4                  XW5      I Sh  v•N u  mnX8-  nU Vs/ s H  oDT;   d  M
  UPM     n	nS[6        4U4S jjn
U	R9                  U
S9  S
nU	 HZ  nTR;                  US5      u  pÍn U R=                  XLU5      I Sh  v•N (       a  US-  nM?  UR?                  UR                  5        M\     [-        5       US'   X±S'   [A        [        R                  " 5       U-
  S-  5      US'   U(       a6  [%        U R                  [+        U5       SSRC                  USS 5       3US9  O[-        5       US	'   S
US'   SUS'   / US'   SUS'   ['        U R                  5      US'   [)        U R                  5        g GN4s  snf ! [         av  n[         R#                  SU R                  U5        [%        U R                  SU 35         SnASUS'   ['        U R                  5      US'   [)        U R                  5        gSnAff = f GN GN GNùs  snf  GN�! [         aR  n[         R#                  SU R                  UR                  USS9  UR?                  UR                  5         SnAGM  SnAff = f! SUS'   ['        U R                  5      US'   [)        U R                  5        f = f7f)u_   Organiza lo que haya en incoming. Todo el cuerpo va en try/finally (ver docstring del mÃ³dulo).r#   NTrÂ   z![%s] No se pudo leer incoming: %szNo se pudo leer incoming: Fr$   r*   r   r-   r+   z![%s] Detectados %d archivos en %sr@   c                 óÄ   >• TU    S   nU(       a  U$  [         R                  " U R                  5       R                  5      $ ! [         a    [         R
                  s $ f = f)Nr;   )r   Úfromtimestampry   rÅ   r{   rT   )r@   rÜ   rÕ   s     €r   Ú	_sort_keyÚ1PhotoOrganizer.process_pending.<locals>._sort_keyR  sS   ø€ Ø˜1‘X˜a‘[�ÞØ�Ið(Ü#×1Ò1°!·&±&³(×2CÑ2CÓDÐDøÜó (Ü#Ÿ<™<Ò'ð(ús   ”-A ÁAÁA)Úkey)NNFr;   z[%s] Fallo organizando %s: %srÐ   r%   r&   iè  r'   z& archivo(s) no se pudieron organizar: rx   rN   )r9   r.   )"r7   r   r|   r·   r¬   Ú	to_threadrË   rj   ru   rm   rn   ro   rp   r€   rÄ   r   rI   rœ   rA   r]   r=   Úlenr   r�   r†   r¿   rÞ   r   ÚsortrS   Ú_process_oner   r_   r‚   )r±   r>   Út0rÖ   r@   rÍ   rL   r•   Úanalysis_failedr9   rã   ÚmovedrÛ   Ú_dtrÝ   rÕ   s                  @r   Úprocess_pendingÚPhotoOrganizer.process_pending/  s<  øé € ä˜Ÿ™Ó(ˆØˆl×ØØˆˆ<ÑÜ�^Š^ÓˆØˆðA	,Ü×#Ò# D×$=Ñ$=Ó>×>Ð>ðØ)-¯©×)>Ñ)>Ô)@ó wÒ)@ AØ!"§¡§ó  Ø12·±·±Ó1AÔEVÑ1Vó  Ø_`×_eÑ_e×_pÑ_pÐqt×_u÷  Ñ)@�
ð wô #& j£/ˆBˆÑÞÜ#-£<��<Ñ Ø+,�Ð'Ñ(Ø#'��<Ñ Øð`  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ôa �K‰KÐ;¸T¿^¹^ÌSÐQ[Ë_Ð^b×^kÑ^kÔlÜ&×0Ò0´ÀÓL×LˆJÞØðV  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ôW %×.Ò.¨t×/BÑ/BÓC×CˆHÜ+2×+<Ò+<¸T×=PÑ=PÐR\Ó+g×%gÑ"ˆE�?ØÑ%ˆFÙ *Ó9¢
˜1°5©j—Q¡
ˆEÐ9ð(œT÷ (ð �J‰J˜9ˆJÑ%àˆEÛ�Ø&+§i¡i°Ð3FÓ&GÑ#�˜ð*Ø!×.Ñ.¨q°jÓA×AÕAØ ™
šàŸ™ a§f¡fÖ-ñ ô !+£ˆBˆ}ÑØ).Ð%Ñ&Ü$'¬¯ªÓ)9¸BÑ)>À$Ñ(FÓ$GˆBÐ Ñ!ÞÜ˜dŸn™nÜ!$ V£ Ð-SÐTX×T]ÑT]Ð^dÐegÐfgÐ^hÓTiÐSjÐkÐsyó{ô $.£<��<Ñ Ø+,�Ð'Ñ(Ø#'��<Ñ Ø%'��>Ñ"ð  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+òA ?ùòwøäó Ü—‘Ð@À$Ç.Á.ÐRUÔVÜ˜dŸn™nÐ0JÈ3È%Ð.PÔQÛðn  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ûðyúò Mò DÚ%gùâ9ò" Bøô !ó *Ü—L‘LÐ!@À$Ç.Á.ÐRS×RXÑRXÐZ]Ðhl�LÑmØ—M‘M !§&¡&×)Ó)ûð*ûð"  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+üs.  ƒ=S
Á#R Á$NÁ%R Á*N  ÂNÂ# NÃNÃ)NÃ/N  Ã1,R Ä3S
ÅAR Æ$P#Æ%R Æ13S
Ç$#R ÈP&È(R È0P)È1R É 	P,ÉP,É;R ÊP4Ê$P1Ê%P4Ê3R Ê5P4ËBR Í%3S
ÎR ÎN  Î 
P Î*:PÏ$R Ï(3S
ÐP Ð R Ð&R Ð)R Ð,R Ð1P4Ð4
RÐ>ARÒR ÒRÒR Ò4SÓS
Ú	file_pathÚqr_detectedrÝ   c              ƒ   ó  #   • U(       a)  U R                  XR                  UR                  -  5        g[        R                  " [
        R                  U R                  UR                  5      I Sh  v•N nU(       d)  U R                  XR                  UR                  -  5        gU R                   ISh  v•N   U(       a  [        R                  " U5      OSnU(       a6  U(       d/  [        R                  SU R                  USS UR                  5        U(       aV  Un[        R                  " [
        R                  U R                  XF5      I Sh  v•N   U[        U R                  5      S   U'   O8[        R                  " [
        R                  U R                  U5      I Sh  v•N nU R                   U-  U-  nUR#                  SSS9  U(       a^  US-  nUR#                  SSS9  U R%                  X�R                  -  5      n	[        R'                  SU R                  XaR                  5        OU R%                  XqR                  -  5      n	U R                  X5      (       d   SSS5      ISh  v•N   g	[(        R*                  " U R                  5        [        R'                  S
U R                  UR                  XF5         SSS5      ISh  v•N   g GNe GN# GN‰ GN8 No N! , ISh  v•N  (       d  f       g= f7f)u`   Coloca una foto. False si se quedÃ³ donde estaba (`_move` no lanza: hay que mirar su resultado).TNu/   [%s] QR sin cÃ³digo vÃ¡lido ignorado (%r) en %sé<   r/   r¥   z.qrz[%s] Tarjeta detectada: %s (%s)Fu   [%s] %s â†’ %s/%s)Ú_moverª   r€   r¬   ræ   r   Úresolve_photographerr   r®   r   Úextract_coderI   rº   Úset_sticky_qrr7   Úget_sticky_qrr©   r«   Ú_uniquer�   r   Úinvalidate_families_cache)
r±   rð   rñ   rÝ   ÚphotographerÚcardÚcodeÚdest_dirÚqr_subÚdests
             r   ré   ÚPhotoOrganizer._process_onez  s/  é € æØ�J‰J�y§.¡.°9·>±>Ñ"AÔBØä$×.Ò.Ô/C×/XÑ/XÐZ^×ZhÑZhÐjs×jxÑjxÓy×yˆÞØ�J‰J�y§.¡.°9·>±>Ñ"AÔBØà—9—9“9Þ:E”9×)Ò)¨+Ô6È4ˆDÞ¦4Ü—‘ÐPÐRV×R`ÑR`ÐbmÐnqÐoqÐbrÐt}÷  uCñ  uCô  DÞØ�Ü×'Ò'Ô(<×(JÑ(JÈDÏNÉNÐ\hÓo×oÐoØVZ”˜DŸN™NÓ+Ð,DÑEÀlÒSä$×.Ò.Ô/C×/QÑ/QÐSW×SaÑSaÐcoÓp×p�à—{‘{ \Ñ1°DÑ8ˆHØ�N‰N 4°$ˆNÑ7ÞØ! EÑ)�Ø—‘ T°D�Ñ9Ø—|‘| F¯^©^Ñ$;Ó<�Ü—‘Ð=¸t¿~¹~Èt×UcÑUcÕdà—|‘| H¯~©~Ñ$=Ó>�Ø—:‘:˜i×.Ñ.Ø÷+ —9�9ô, ×3Ò3°D·N±NÔCÜ�K‰KÐ+¨T¯^©^¸Y¿^¹^È\Ô`Ø÷1 —9�9ò zò
 ò pò q÷ —9—9’9üs¡   ‚A.K?Á0KÁ1AK?Â5KÂ6K?Â9BK%ÅKÅAK%Æ&KÆ'B?K%É&K?É1K!É2K?É7AK%ËK?ËK#ËK?ËK?ËK%ËK%Ë!K?Ë#K?Ë%K<Ë+K.Ë,K<Ë8K?r‡   c                 óÂ   • U Sp!UR                  5       (       aF  U R                  U R                   SU U R                   3-  nUS-  nUR                  5       (       a  MF  U$ )Nr;   Ú_)ÚexistsÚparentÚstemrn   )r‡   ÚpÚns      r   rù   ÚPhotoOrganizer._uniqueŸ  sV   € à�Qˆ1Ø�h‰h�j‰jØ—‘ §¡ ¨1¨Q¨C°·±¨}Ð=Ñ=ˆAØ�‰FˆAð �h‰h�j‹jð ˆr1   rÚ   Údstc                 ó”  •  UR                   R                  SSS9  [        R                  " [	        U5      [	        U5      5        g! [
         a9    [        R                  SU R                  UR                  UR                   5         g[         a6  n[        R                  SU R                  UR                  U5         S nAgS nAff = f)NTr¥   z/[%s] Error de PERMISOS al mover %s (carpeta %s)z[%s] Error moviendo %s: %sF)r  r«   ÚshutilÚmover™   ÚPermissionErrorrI   rœ   r   r€   r   )r±   rÚ   r
  rL   s       r   rô   ÚPhotoOrganizer._move§  s¡   € ð	VØ�J‰J×Ñ T°DÐÑ9Ü�KŠKœ˜C›¤# c£(Ô+ØøÜó 	rÜ�L‰LÐJÈDÏNÉNÐ\_×\dÑ\dÐfi×fpÑfpÕqð ô ó 	VÜ�L‰LÐ5°t·~±~ÀsÇxÁxÐQT×UÐUØûð	Vús   ‚AA ÁA CÂ	CÂ,CÃC)r¯   r°   rª   rj   r®   r©   r   )r   r´   ©r   N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__r™   r   r²   r¿   rË   r\   ÚtuplerE   rÞ   rî   r   rR   ré   Ústaticmethodrù   rô   Ú__static_attributes__r0   r1   r   rŸ   rŸ   î   s¶   † ð	  #ð 	 °Tð 	 Àtô 	 ôô]ð.¨ð ¸ð ÈÈdÐTXÈjÑHYô ô I,ðV#¨Dð #¸xÈ¹}ð #ÐZ^ð #Ðcgô #ðJ ð�dð ˜tó ó ðð	˜ð 	 Dð 	¨T÷ 	r1   rŸ   c                   ó~   • \ rS rSrSrS\S\R                  4S jrS r	S r
S rS	 rS
 rS\R                  4S jrSrg)Ú_Handleriµ  zªRebota los eventos de disco al event loop. watchdog los llama desde SU hilo, donde `call_later` y
`create_task` no son thread-safe: todo entra por `call_soon_threadsafe`.ÚorgÚloopc                 ó*   • Xl         X l        S U l        g r4   )Ú_orgÚ_loopÚ_timer)r±   r  r  s      r   r²   Ú_Handler.__init__¹  s   € ØŒ	ØŒ
Øˆ�r1   c                 óH   • UR                   (       d  U R                  5         g g r4   ©Úis_directoryÚ_reset_timer©r±   Úevents     r   Úon_modifiedÚ_Handler.on_modified¾  ó   € Ø×!×!Ø×ÑÕð "r1   c                 óH   • UR                   (       d  U R                  5         g g r4   r"  r%  s     r   Ú
on_createdÚ_Handler.on_createdÂ  r)  r1   c                 óp   •  U R                   R                  U R                  5        g ! [         a     g f = fr4   )r  Úcall_soon_threadsafeÚ	_scheduleÚRuntimeError©r±   s    r   r$  Ú_Handler._reset_timerÆ  s/   € ð	Ø�J‰J×+Ñ+¨D¯N©NÕ;øÜó 	Ùð	ús   ‚%( ¨
5´5c                 óÌ   • U R                   (       a  U R                   R                  5         U R                  R                  [        R
                  U R                  5      U l         g r4   )r  Úcancelr  Ú
call_laterr
   ÚWATCHDOG_DEBOUNCE_SECSÚ_firer1  s    r   r/  Ú_Handler._scheduleÌ  s;   € Ø�;�;Ø�K‰K×ÑÔ Ø—j‘j×+Ñ+¬F×,IÑ,IÈ4Ï:É:ÓVˆ�r1   c                 ó    • U R                   R                  U R                  R                  5       5      nUR	                  U R
                  5        g r4   )r  Úcreate_taskr  rî   Úadd_done_callbackÚ_on_done)r±   Útasks     r   r7  Ú_Handler._fireÑ  s5   € Ø�z‰z×%Ñ% d§i¡i×&?Ñ&?Ó&AÓBˆØ×Ñ˜tŸ}™}Õ-r1   r=  c                 óø   • UR                  5       (       a  g UR                  5       nU(       aM  [        R                  SU R                  R
                  X"S9  [        U R                  R
                  SU 35        g g )Nu#   [%s] Pasada del watchdog fallÃ³: %srÐ   u   Pasada del watchdog fallÃ³: )Ú	cancelledÚ	exceptionrI   rœ   r  r   rA   )r±   r=  rL   s      r   r<  Ú_Handler._on_doneÕ  s`   € Ø�>‰>×ÑØØ�n‰nÓˆÞÜ�L‰LÐ>ÀÇ	Á	×@SÑ@SÐUXˆLÑgÜ˜$Ÿ)™)×-Ñ-Ð1MÈcÈUÐ/SÕTð r1   )r  r  r  N)r  r  r  r  Ú__doc__rŸ   r¬   ÚAbstractEventLoopr²   r'  r+  r$  r/  r7  ÚTaskr<  r  r0   r1   r   r  r  µ  sM   † ñPð˜Nð °'×2KÑ2Kô ò
 ò òòWò
.ðU˜WŸ\™\÷ Ur1   r  c           
   ƒ   ó°  #   • [        U 5      nSUS'   [        5       US'    [        U 5      nUR                  SSS9  [	        X[
        R                  " U 5      5      n[        R                  S
X5        [        U 5        S	n [        5       nUR                  [        U[         R"                  " 5       5      [%        U5      SS9  UR'                  5         Sn  U(       a  [         R.                  " U5      I S	h  v•N    UR1                  5       I S	h  v•N   [(        R*                  nMP  ! [         a>  n[        R                  SXSS9  [        U SU 35        SUS'   [        U 5         S	nAg	S	nAff = f! [         aY  n[        R                  SU [(        R*                  U5        [        U S[-        [(        R*                  5       SU 35         S	nANýS	nAff = f Nà NÉ! [         R2                   a    e [         ar  n[        U 5      S   S-   n[        U SU 35        [5        [(        R6                  S[5        US-
  S5      -  -  [8        5      n[        R                  SXXdSS9   S	nAGNBS	nAff = f! [         R2                   a    [        R                  SU 5        e f = f! SUS'   [        U 5        Ub"   UR;                  5         f ! [         a     f f = ff = f7f)uS   Bucle de un colegio. **No termina nunca por un error** (ver docstring del mÃ³dulo).Tr(   r)   r¥   z&[%s] No se pudo crear el organizer: %srÐ   z!No se pudo iniciar el organizer: FNz[%s] Organizer iniciado en %s)Ú	recursivez5[%s] Watchdog no disponible, solo sondeo cada %ss: %sz$Watchdog no disponible (sondeo cada zs): r¨   r-   r;   u   Ciclo del organizer fallÃ³: é   r   uE   [%s] Ciclo del organizer fallÃ³ (%d seguidos), reintento en %.0fs: %sz[%s] Organizer detenido)r7   r   r    r«   rŸ   r   Úschool_rootr   rI   rœ   rA   r=   r�   r	   Úscheduler  r¬   Úget_running_loopr™   Ústartr
   rU   r_   r}   rî   ÚCancelledErrorÚminÚORGANIZER_RETRY_SECSÚ_BACKOFF_MAXÚstop)r   r>   rj   Ú	organizerrL   ÚobserverÚdelayÚerrorss           r   Úrun_organizerrV  Þ  s§  é € ä	�YÓ	€BØ€B€w�KÜ!“|€B€|Ñð	Ü# IÓ.ˆØ�‰˜t¨dˆÑ3Ü" 9¼×8QÒ8QÐR[Ó8\Ó]ˆ	ô ‡K�KÐ/°ÔEÜ�IÔà€HðtÜ“:ˆØ×Ñœ( 9¬g×.FÒ.FÓ.HÓIÌ3ÈxË=ÐdiÐÑjØ�‰Ôð €EðØÞÜ—m’m EÓ*×*Ð*ð
KØ×/Ñ/Ó1×1Ð1Ü×2Ñ2�ñ øô- ó Ü�‰Ð=¸yÐX\ˆÑ]Ü�iÐ#DÀSÀEÐ!JÔKØˆˆ7‰Ü˜	Ô"Üûðûô ó tä�‰ÐLÈiÔY_×YsÑYsÐuxÔyÜ�iÐ#GÌÌF×LfÑLfÓHgÐGhÐhlÐmpÐlqÐ!r×sÑsûðtúñ +á1øä×)Ñ)ó ØÜó KÜ$ YÓ/Ð0DÑEÈÑI�Ü˜iÐ+GÈÀuÐ)MÔNÜœF×7Ñ7¸1ÄÀFÈQÁJÐPQÓ@RÑ;RÑSÔUaÓb�Ü—‘ÐdØ&°ÀTð ÷ Kûð	Kûô ×!Ñ!ó Ü�‰Ð-¨yÔ9Øðûð ˆˆ7‰Ü˜	Ô"ØÑðØ—‘•øÜó Ùðúð  üsë   ‚K¡;D% Á#KÂ AE0 ÃKÃ!I- Ã5GÃ6I- Ã;G ÄGÄG Ä#I- Ä%
E-Ä/4E(Å#KÅ(E-Å-KÅ0
GÅ:AGÇ	KÇGÇKÇI- ÇG ÇI*Ç8A'I%ÉI- É%I*É*I- É-,JÊJ ÊKÊ1KËKË
KËKËKËKËKc               ƒ   óh  #   • 0 n    [         R                  " [        R                  S5      I Sh  v•N  Vs1 s H  oS   iM	     nn[        U5       H¢  nU R                  U5      nUb  UR                  5       (       d  M.  UbN  UR                  5       (       d9  UR                  5       (       a$  [
        R                  SXER                  5       5        [         R                  " [        U5      SU 3S9X'   M¤     U  Vs/ s H  oU;  d  M
  UPM     sn H8  nU R                  U5      R!                  5         [
        R#                  S	U5        M:     [         R$                  " [&        R(                  5      I Sh  v•N   GMv   GNMs  snf ! [         a,  n[
        R                  SU5        [        U 5      n SnAGNoSnAff = fs  snf  NM! [         R*                   aV    U R-                  5        H  nUR!                  5         M     [         R.                  " U R-                  5       S
S06I Sh  v•N    e f = f7f)zbUn organizer por colegio activo. Detecta colegios nuevos o desactivados sin reiniciar el servicio.TFNÚidz4Gestor de organizers: no se pudo listar colegios: %su5   [%s] Organizer terminÃ³ con error: %s â€” se reiniciaz
organizer-)r€   z,[%s] Colegio desactivado: organizer detenidoÚreturn_exceptions)r¬   ræ   r   Úlist_schoolsr   rI   rœ   r<   r¼   rS   Údoner@  rA  r:  rV  Úpopr4  r�   r}   r
   ÚORGANIZER_RESCAN_SECSrM  ÚvaluesÚgather)ÚtasksÚsÚactiverL   Úsidr=  Úts          r   Úrun_all_organizersre    sµ  é € à%'€EðØð$Ü18×1BÒ1BÄ>×C^ÑC^Ð`eÓ1f×+fÐ+fÓgÒ+f a˜Dœ'Ñ+f�Ðgô ˜f–~�Ø—y‘y “~�Ø‘< 4§9¡9§;£;ØÑ'°·±×0@Ñ0@ÀTÇ^Á^×EUÑEUÜŸ™Ð%\Ð^a×cqÑcqÓcsÔtÜ!(×!4Ò!4´]À3Ó5GÐPZÐ[^ÐZ_ÐN`Ñ!a�E“Jñ &ñ $)Ó<¢5˜a°V©OŸ¡5Ô<�Ø—	‘	˜#“×%Ñ%Ô'Ü—‘ÐJÈCÖPñ =ô —-’-¤× <Ñ <Ó=×=Ð=ò â+fùÒgøÜó $Ü—‘ÐSÐUXÔYÜ˜U›–ûð$üò =ñ >øÜ×!Ñ!ó Ø—‘–ˆAØ�H‰HŽJñ  ä�nŠn˜eŸl™l›nÐEÀÑE×EÑEØð	üs“   ‚H2†G ˆ(F °E=±F ·F ÁF Á5G Â A<G Ã<	F>Ä	F>ÄA&G Å5GÅ6G Å=F Æ F Æ
F;Æ!F6Æ0G Æ6F;Æ;	G ÇA"H/È'H*È(H/È/H2r4   r  )ErC  r¬   rF   ÚloggingÚrer  r|   Úconcurrent.futuresr   r   r   Úpathlibr   Útypingr   r—   Úwatchdog.eventsr   Úwatchdog.observersr	   Úcorer
   Úcore.dbr   Úcore.timeutilr   r   Úservicesr   r   r   r   Ú	getLoggerr  rI   ÚCV2_AVAILABLErN  Ú
__import__Ú	cpu_countrÒ   ÚIMAGE_EXTENSIONSro   rp   rÃ   r¸   rP  r™   r    r!   rE   Ú__annotations__r2   r7   r\   rA   r=   rX   re   ri   r_   r]   r†   r”   r  r�   rŸ   r  rV  re  )Úes   0r   Ú<module>rx     s  ðòó: Û Û Û 	Û Û ß ?Ý Ý Ý ã 
Ý 2Ý 'å Ý ß ,ß SÓ Sà	×	Ò	˜8Ó	$€à€áˆq‘:˜dÓ#×-Ñ-Ó/×4°1Ó6€Ø×*Ñ*Ð Ù(8Ó9Ò(8 1—W‘W–YÑ(8Ñ9Ð à�8Ð Ø€Ø€ð

+ ð 
+¨ô 
+ð &(Ð �4˜˜T˜	‘?Ó 'ð�Tô ðB˜3ð B 4ô Bñ˜Sð ¨3ð °xÀ±~ð ÐQUõ ð
M˜sð 
M tô 
Mð˜sð  x°¡~ô ð Cð ¨Dô ñ8+˜( 3™-ð +°4õ +ð˜Tð  cô ð˜$ð  4ô ð2˜ð  (¨8Ñ"4ô ñ 
'˜ð 
'Ð(9ð 
'ÀUõ 
'÷Bñ BôN&UÐ%ô &UðR7 3ð 7¨4ô 7õtùòA :s   Â0F