Spaces:
Running
Running
Commit History
-
Sesiones revocables: logout real y cierre en todos los dispositivos
Jose Salazar +1: Claude Opus 5ARCHITECTURE_REVIEW §2.3. Las sesiones son cookies firmadas sin estado y el logout se limitaba a borrar la cookie del navegador: el token seguía siendo VÁLIDO allá donde se hubiera copiado, hasta `session_max_age_s` (8h). No había forma de invalidar una sesión filtrada ni de echar a nadie tras un incidente, salvo rotar MORPHOS_SESSION_SECRET, que tira a todos los usuarios a la vez. Dos mecanismos, porque resuelven cosas distintas (migración 4): - `sesiones_revocadas`: una sesión concreta, por `jti`. Se recuerda sólo hasta que su firma caduca por sí sola; a partir de ahí la fila sobra y se poda. - `usuarios.sesiones_validas_desde`: TODAS las de una cuenta a la vez, con el nuevo POST /api/auth/logout-todas. Un sello temporal en vez de un contador de versión porque la pregunta es «¿se emitió antes del corte?». `jti` y `emitida_en` los añade `firmar_sesion`, no quien llama: si se generaran fuera, una ruta podría emitir por descuido una sesión irrevocable. TIMESTAMPS EN PYTHON, NO …2f5c422 -
Aislar por clínica los resultados de analizador
Jose Salazar +1: Claude Opus 5ARCHITECTURE_REVIEW §2.1 (+ la parte de identidad de §2.9). Era el hallazgo más grave de la revisión: `lab/almacen.py` era un dict global sin dueño y `routers/lab.py` no filtraba nada, así que CUALQUIER sesión autenticada podía leer la muestra de cualquier clínica, y `/api/lab/pendientes` las enumeraba todas —sin necesidad siquiera de adivinar IDs—. Cada `muestra_id` abre el panel completo de analitos más las pistas de paciente (nombre de la mascota, raza, sexo). IDENTIDAD DE DISPOSITIVO. `lab_api_keys` pasa de lista de secretos anónimos a `clinica:clave`. `verificar_dispositivo` devuelve el tenant dueño en vez de None, y el recorrido sigue siendo en tiempo constante sobre TODAS las claves (un lookup por diccionario sobre un secreto filtra por tiempo). Revocar = quitar la clave y reiniciar. DE DÓNDE SALE EL TENANT. Del servidor, siempre: de la API key en la ingesta y de la cookie FIRMADA en la lectura. Nunca del cuerpo de la petición —si el puente pudiera declarar su clínica, mentir …1339cdc -
Esquema versionado y BD de usuarios en volumen persistente si lo hay
Jose Salazar +1: Claude Opus 5ARCHITECTURE_REVIEW §3.4. MIGRACIONES. El esquema era un único `CREATE TABLE IF NOT EXISTS`: creaba las tablas en una BD vacía y no hacía NADA sobre una existente, así que añadir una columna era una operación manual sobre un fichero al que, en Spaces, nadie puede llegar. Ahora `PRAGMA user_version` con pasos numerados; sólo se aplican los pendientes. Los pasos son reejecutables sobre lo ya existente porque las BD creadas antes de esto están en la versión 0 con las tablas del paso 1 ya dentro —ese es el caso de la instancia desplegada, y hay prueba de que se pone al día sin perder cuentas—. Paso 2: índice para el throttle de login, que filtraba por email+ip+momento con scan completo en cada intento. PERSISTENCIA. `db_path` apuntaba siempre a `instance/`, que en Spaces no sobrevive al reinicio: cada rebuild se llevaba cuentas, hashes e historial. Ahora se elige /data si hay un volumen persistente montado ahí, y si no se cae a `instance/` AVISANDO al arrancar. Se elige solo porque el fa…b90d0d2 -
IP real detrás del proxy y caché de papers atómica y acotada
Jose Salazar +1: Claude Opus 5ARCHITECTURE_REVIEW §2.6 y §2.5. PROXY (§2.6). El limitador usaba `request.client.host`, que detrás de un proxy —HF Spaces tiene uno— es la dirección del PROXY. `limite_login` (5/minute) y `limite_papers` no eran por IP sino GLOBALES: a la vez un bypass (fuerza bruta desde muchas IPs contra un único contador) y una auto-denegación de servicio (un cliente ruidoso agota el login de todos). El throttle por email+IP de `auth.py` tenía el mismo problema. `X-Forwarded-For` NO se lee a ciegas: la manda el cliente, y creérsela permite falsificar la IP en cada petición. El operador declara cuántos proxies hay delante (`MORPHOS_PROXY_SALTOS_CONFIABLES`, 0 por defecto) y se toma el elemento -N, que es el último escrito por infraestructura de confianza; lo que hay a la izquierda lo controla el cliente y se descarta. Aviso al arrancar si en prod sigue a 0, porque el fallo es silencioso: los límites "funcionan", sólo que compartidos. CACHÉ DE PAPERS (§2.5). Tres fallos: - `write_text` no es atómi…5118fc3 -
Merge pull request #2 from josesalazar2025/perf/bucle-de-eventos-no-bloqueante
Jose SalazarUnverified signatureSacar del bucle de eventos la recuperación RAG, SQLite y scrypt6ab0814 -
Sacar del bucle de eventos la recuperación RAG, SQLite y scrypt
Jose Salazar +1: Claude Opus 5ARCHITECTURE_REVIEW §3.1. `interpretar` es `async`, pero sus dos operaciones más caras eran SÍNCRONAS y corrían en el bucle de eventos, así que mientras una interpretación recuperaba, el proceso entero no contestaba a NADA: ni health check, ni el login de otro veterinario, ni una ingesta del puente. La concurrencia efectiva era 1 en una app que se anuncia multiusuario. - `ai/service.py`: `recuperar` / `recuperar_multi` a un hilo. Es lo más caro del request —embedding bge-m3, búsqueda LanceDB y cross-encoder sobre hasta `rag_candidatos` filas—, del orden de segundos en cpu-basic. - `routers/auth.py`: SQLite y scrypt de login y alta. scrypt (n=2**14) es caro a propósito: es justo el trabajo que no puede vivir en el bucle. - `routers/lab.py`: la escritura de `lab_persistir`, que corre por cada muestra con el analizador enviando en ráfaga (120/minute). `security/authz.py` no toca la BD (la sesión es una cookie firmada), así que las peticiones autenticadas ya no pasaban por SQLite…e26316b -
Merge pull request #1 from josesalazar2025/ci/puerta-de-codigo
Jose SalazarUnverified signatureCi/puerta de codigof61b36f -
Cerrar el alta de cuentas y apagar la cola de muestras por defecto
Jose Salazar +1: Claude Opus 5Contención inmediata de ARCHITECTURE_REVIEW §2.2 y §2.1. Ninguna de las dos es el arreglo estructural —ese es el scoping por tenant— pero ambas reducen hoy la superficie sin tocar el modelo de datos. ALTA CERRADA (§2.2). Cualquiera podía POSTear /api/auth/registro y alcanzar /api/interpret, que gasta cuota de ZeroGPU compartida y, por la ruta Claude, dinero real: `limite_interpret_usuario` era un techo sobre una identidad que costaba una petición HTTP acuñar. Ahora `registro_abierto=False` + `registro_allowlist`. Dos detalles que no son cosméticos: - La comprobación de allowlist va ANTES que la de existencia. Al revés, un email no aprobado distinguiría 409 (existe) de 403 (no existe) y el alta sería un oráculo de la base de usuarios. Fuera de la lista: 403 siempre. - Es una lista de emails y no un booleano porque `instance/` es efímero (§3.4): las cuentas desaparecen en cada reinicio, así que un alta cerrada sin lista dejaría la instancia sin acceso posible. Se avisa al arran…61cd0db -
CI: puerta de código en todos los PR (ruff, pytest, tsc, eslint)
Jose Salazar +1: Claude Opus 5Antes, un PR que tocara sólo `backend/app/security/` o `backend/app/routers/` no disparaba NINGÚN workflow: el único que existía filtraba por rutas y nombraba `backend/app/ai/**` y `backend/app/rag/**`. Toda la superficie de autenticación, sesiones, CSRF e ingesta de laboratorio se fusionaba sin que nadie ejecutara `backend/tests/`, que nunca ha corrido en CI pese a estar en `make backend-test`. `ci.yml` corre en todos los PR, sin filtro: ruff + pytest (backend) y tsc --noEmit + eslint (frontend). El alcance de ruff es el de `make lint` (evals/ y bridge/ fuera a propósito). Sin `--group rag`: las dos suites que lo necesitan ya se auto-omiten y los grupos pesados costarían GBs por una prueba sintética. `evals.yml` conserva su filtro —es la puerta cara— pero se amplía a `backend/**` y `frontend/**`: el anterior dejaba fuera `schemas.py`, que define la forma misma que las evals puntúan, y `config.py`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>523cb7e -
CLAUDE.md: invariantes de pdf.js, panel-vacio y léxico clínico
Jose Salazar +1: Claude Opus 5Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>eaa81c2 -
Pruebas unitarias de los scripts de evals y target `make evals-unit`
Jose Salazar +1: Claude Opus 5Antes de la puerta, la puerta misma: métricas, rúbrica, umbrales y esquema del dataset son código sin cobertura de nadie más. Si el medidor está roto, el resultado de la puerta no significa nada —visto: el simulador dejaba `fuera_de_alcance` sin declarar y suspendía por su cuenta una métrica de tolerancia cero—. `evals/tests/` no necesita modelo, juez ni índice: son puras. Corren con el venv del backend porque los scripts importan `app.*`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>af530a3 -
pdf.js 4.x vendorizado y pruebas del extractor de PDF
Jose Salazar +1: Claude Opus 5La 3.x leía mal los CMap `ToUnicode` con destinos de un byte —fuera de especificación, pero los emiten informes de laboratorio reales generados con Ghostscript— y devolvía cada carácter desplazado 8 bits ('H' → U+4800), con lo que la importación fallaba entera con «No se encontraron datos reconocibles». El desplazamiento NO se puede reparar a posteriori: el '0' desplazado cae en U+3000 y el extractor lo normaliza a espacio, así que una ALT de 260 U/L se leería como 26. Por eso `textoIlegible()` detecta el caso y aborta en vez de importar números equivocados. pdf.js 4+ se distribuye como módulo ES y ya no publica `window.pdfjsLib`, de ahí el paso de .js a .mjs y la carga dinámica. La versión es un invariante: los ficheros de `assets/lib/pdfjs/` se copian de la devDependency con `npm run vendor-pdfjs` para que navegador y pruebas usen la misma. `frontend/tests/pdf-parser.test.ts` fija todo esto con un PDF construido con esa anomalía, y cubre la ventana de aceptación de valores: sólo en…c0b119e -
Estado vacío de los paneles de exámenes en escritorio
Jose Salazar +1: Claude Opus 5`panel-vacio.ts` sustituye el formulario por una zona de adjuntar (arrastrar / explorar / «Insertar resultados manualmente») mientras el `<section>` lleva la clase `.sin-datos`. Sólo en escritorio: el CSS que lo enciende vive dentro de `@media (min-width: 1101px)`. Depende sólo de `dom.ts` (`esGridEscritorio`, extraído ahí para que lo compartan `ui.ts`, `lab-import.ts` y este módulo). Lo importa `form-inject.ts`, base común de los dos importadores, que llama a `revelarPanelDeCampo()` por cada valor inyectado: un panel que recibe datos sale del estado vacío solo. Colgarlo de `ui.ts` —que toca el DOM al cargarse— rompería sus pruebas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>e4a79ec -
Léxico clínico compartido y detección de analitos fabricados
Jose Salazar +1: Claude Opus 5`ai/lexico.py` centraliza el vocabulario clínico (variantes, abreviaturas, nombres) que hasta ahora vivía disperso, y `ai/coherencia.py` lo usa para la comprobación que faltaba: distinguir «no se midió» de «se midió y salió en rango». Sin ese dato el modelo rellenaba el hueco —medido el 2026-08-04: medGemma afirmó un leucograma con neutrofilia sobre un panel de calcio/fósforo/ BUN/creatinina, y llamó «trombocitopenia leve» a unas plaquetas en rango—. De ahí `analitos_medidos` en `PeticionInterpretacion` y en el cliente (`ia.ts`): el panel COMPLETO, no sólo lo alterado. Campo opcional, así que un cliente antiguo que no lo mande se comporta como antes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>7dba92c
-
Agrego boton para el syncing de dispositivos de laboratorio en la UI de desktop
Jose Salazar1fec72e -
Allow list para modelos locales configurables por el usuario
Jose Salazar6ab7946 -
Publicar el índice curado y evitar que el Hub acumule ficheros huérfanos
Jose Salazar +1: Claude Opus 5Al publicar el índice curado (6772 → 6201 fragmentos) aparecieron dos fallos en la ruta de publicación: - `upload_folder` no borraba lo que ya no existe en local. LanceDB versiona por ficheros, así que al compactar desaparecen los datos viejos y el remoto se los quedaba: el repo tenía 2 ficheros de datos y 2 índices FTS huérfanos, 73 MB para servir 33 MB — que además se hornean en la imagen. Ahora el remoto es un espejo del local (`delete_patterns="*"`). - `manifest.json` lo escribe la ingesta, pero `curar_indice.py` cambia el número de filas después. Publicaba un recuento desfasado sin romper nada visible, que es lo peligroso: el mensaje de commit del Hub y `fetch-index` anunciaban 6772 fragmentos sobre un índice de 6201. `curar_indice.py` lo reconcilia y `publish-index` aborta si no cuadra. Verificado descargando el índice publicado en limpio: 13 ficheros, 33 MB, los 51 tests de alcance en verde y la eval de recuperación en 0.814/0.941/0.912. Co-Authored-By: Claud…02b6489 -
Ajustes varios al engine y al prompt basado en resultados de los evals
Jose Salazarbf9f7d1
-
Implementacion de AI judge para evals y actualizacion de readme
Jose Salazar1e215c9
-
No reintentar el 429 y limitar /api/interpret también por usuario
Jose Salazar +1: Claude Opus 5Dos cambios que atacan lo mismo: la cuota de GPU compartida es el recurso escaso de una instancia pública gratuita, y hoy se malgasta. 1) El reintento no distinguía qué fallo estaba tratando. service.py reintentaba ante cualquier ErrorModelo. Para la salida malformada de medGemma (razonamiento filtrado, bucle de repetición) eso es correcto y es su razón de ser. Para un 429 es contraproducente: gasta otra reserva de GPU del mismo pozo agotado, justo cuando el Space pide parar. Para un rechazo por seguridad o una clave ausente, no puede cambiar nada. ErrorModelo pasa a llevar `reintentable` y `saturado`; cada punto de fallo se clasifica (429 y cuota → no reintentable; 5xx → sí; config y rechazos → no). Un 429 ahora cuesta UNA llamada en vez de dos. /api/interpret responde 503 + Retry-After ante saturación, no un 502 genérico: el 502 invita a recargar en bucle contra una cuota ya agotada. 2) El rate limit era sólo por IP. ZeroGPU es por cuenta, no por …a5d73ff -
Autenticar también la precarga de modelos en la build
Jose Salazar +1: Claude Opus 5El RUN que hornea bge-m3 y bge-reranker-v2-m3 descargaba sin credencial. El log de la build lo avisaba: Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads. Los modelos son públicos, así que funcionaba, pero anónimo cae en el límite de tasa del Hub — y son 6.4 GB, justo donde eso muerde. Se monta el mismo secreto hf_token que ya usa la descarga del índice. `required=false`: sin el secreto la build sigue funcionando (sólo más lenta). El montaje vive únicamente durante el RUN, así que el token no queda en ninguna capa de la imagen —a diferencia de un --build-arg. Asignación y export separados para no enmascarar el código de salida de `cat` (SC2155, señalado por hadolint). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>843a295 -
Corregir afirmación falsa sobre la guarda de auth en /api/papers
Jose Salazar +1: Claude Opus 5La auditoría afirmaba «Depends(usuario_actual) en interpret/papers (401 verificado)». Verificado contra el despliegue y en local: papers NO tiene guarda; /api/papers?query=anemia responde 200 sin sesión. Sólo lo protege el rate limit de 30/min por IP. No es una regresión —el punto del plan pedía «auth en interpret, rate-limit en papers»— pero la anotación de estado sobreafirmaba, y no hay clave de API en juego (eutils se consulta sin credencial). Se documenta además que el bloqueo de CORS, correcto en la app (verificado en local: un origen no listado no recibe ACAO), queda neutralizado en producción por el proxy de HF Spaces, que añade ACAO y expose-headers por su cuenta. Ahí la defensa efectiva es SameSite=Strict en las cookies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>dd5a294 -
Arreglar .dockerignore: la imagen no llegaba a construirse
Jose Salazar +1: Claude Opus 5Dos fallos, ambos preexistentes y ambos fatales para el despliegue nuevo. 1) El venv de macOS sobrescribía el de Linux. `RUN uv sync` construye /app/backend/.venv (Linux) y el `COPY backend/ backend/` posterior lo pisaba con el .venv local (binarios Mach-O, 1.3 GB), dejando un python inejecutable en el contenedor. La build habría reventado al precargar los modelos y, de haber arrancado, el CMD de uvicorn habría fallado igual. 2) backend/.env se horneaba en la imagen. .dockerignore usa la semántica de filepath.Match de Go, donde `*` NO cruza el separador de ruta: `.env` y `*.env` excluían el de la raíz pero no backend/.env, que contiene HF_API_KEY. Se añaden los patrones `**/`. De paso se excluyen books/ (226 MB con licencia), node_modules anidados, cachés de Python y los directorios que la imagen no usa. Contexto de build: de 40.128 archivos a 221 (77 MB, casi todo el índice RAG). Nota: sigue sin poder construirse aquí para verificarlo (4,1 GB de disco libre f…a2a4438 -
No tragar errores de programación al leer la sesión
Jose Salazar +1: Claude Opus 5leer_sesion capturaba (BadSignature, Exception). El `Exception` hacía redundante el `BadSignature` y, sobre todo, convertía cualquier error interno en «no hay sesión»: un bug en la capa de sesión salía como un 401 silencioso en vez de propagarse, que es justo lo que no se quiere en el camino de autenticación. Se captura BadData, el padre común de: - BadSignature firma inválida o falsificada - SignatureExpired sesión caducada (subclase de BadSignature) - BadPayload token corrupto Las tres son «cookie no válida» → sin sesión. Un TypeError o un fallo de configuración ahora se propagan. Nota: (BadSignature, SignatureExpired) —la corrección que se propuso primero— habría repetido el mismo error en pequeño, porque SignatureExpired ya es subclase de BadSignature. Se añaden tests de sesión (no había ninguno), incluido el caso negativo que motiva el cambio: un error interno debe propagarse, no devolver None. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>5af9605 -
Merge remote-tracking branch 'origin/main'
Jose Salazar# Conflicts: # CLAUDE.mdf651345 -
Actualizar documentación al stack nuevo
Jose Salazar +1: Claude Opus 5CLAUDE.md describía js/analisis.js y api/hf_proxy.php como vigentes; se reescribe al flujo real (bundle TS → /api/interpret → RAG → modelo) y se añade la sección de distribución del corpus. README.md se marca como histórico: describe la entrega XAMPP del curso, cuyas instrucciones de instalación ya no son válidas. No se reescribe entero porque incluye la memoria académica del proyecto. PLAN_MODERNIZACION.md refleja la auditoría del 2026-07-26: Fase 3 (RAG) pasa a completa —búsqueda híbrida, rerank y corpus real de 6763 fragmentos— y se detalla lo que sigue abierto en Fase 4 (Ragas sin implementar, split reservado, human-in-the-loop). .gitignore: books/* salvo el README, para que un formato nuevo de corpus no se cuele por no estar listado; y .agents/ (tooling local del asistente). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>ce79810 -
Distribución del corpus RAG vía Hub privado y horneado de modelos
Jose Salazar +1: Claude Opus 5Los libros con licencia (226 MB) y el índice LanceDB (70 MB) no pueden ir en git. Viven en dos datasets PRIVADOS del Hub; scripts/hub.py los publica y los descarga. Se usa la API de Python de huggingface_hub y no el CLI `hf`: en la versión instalada (1.16.1) el CLI devuelve código 1 aunque la operación vaya bien, por una incompatibilidad typer/click, lo que abortaría cualquier Makefile o build. Dockerfile: - WITH_RAG pasa a 1 por defecto. Con 0, el retriever degradaba en silencio y las interpretaciones salían sin fundamentar sin que nada lo indicara. - Los modelos (bge-m3 + bge-reranker-v2-m3, ~6.4 GB) se hornean en /opt/hf y se fija HF_HUB_OFFLINE=1 en runtime, para que un fallo de red sea ruidoso en vez de degradar la recuperación a orden RRF sin avisar. - Si instance/rag_index no está en el contexto de build, se descarga con HF_TOKEN pasado como secreto de build (nunca --build-arg, que quedaría grabado en el historial de capas). NOTA: el Dockerfile no se ha podido const…aa0d853 -
Añadir el stack nuevo: Vite/TypeScript + FastAPI
Jose Salazar +1: Claude Opus 5frontend/ Motor de patrones portado a TS (analisis.ts) con 27 tests dorados que fijan el comportamiento validado por veterinarios, más el resto de módulos (ui, pdf-parser, auth, papers, lab-import, ia). backend/ Servicio FastAPI: salida estructurada con Pydantic, clientes medGemma/Claude, recuperación RAG y toda la seguridad (sesión, CSRF, rate limit, CORS cerrado, BD fuera del webroot). bridge/ Puente local ASTM/HL7 para analizadores de laboratorio. evals/ Arnés de evaluación clínica + puerta de CI. index.html pasa a cargar el bundle TS en lugar de js/main.js. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>70e641d -
Retirar el stack legacy PHP/Apache
Jose Salazar +1: Claude Opus 5js/*.js y api/*.php ya no se cargaban: index.html apunta al bundle TS y el Dockerfile no los copia a la imagen. Eran código muerto (2528 líneas). .htaccess se retira también: era config sólo-Apache y el runtime nuevo es uvicorn. Además protegía secretos por FilesMatch, algo en lo que el plan de seguridad decidió explícitamente no confiar. El código sigue disponible en el tag v1.0-legacy-php y en la rama legacy/php-apache. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>2ab057f -
Delete api/hf_proxy.php
Verified signature1544263
-
Delete CLAUDE.md
Verified signaturea3bacb4 -
Delete USO_DE_IA.md
Verified signatured74800f
-
Expand analyzer coverage: coagulation, blood gas, full urinalysis panels
Jose Salazar +1: Claude Sonnet 4.6Add 33 new parameters from Catalyst ONE, VetScan VS2 and VCHECK V200: - Biochemistry: AMY, LDH, cPLI/fPLI, Mg, Fe, UA, CRP, SAA, TDM (phenobarbital, cyclosporine) - Endocrine: fT4, TSH, progesterone, cTnI, NT-proBNP - Urinalysis (VetScan UA): ketones, blood, bilirubin, urobilinogen, nitrites, leukocyte esterase, RBC/WBC sediment, UPC - Coagulation: PT, aPTT, ACT, fibrinogen, D-dimer, FDP, AT-III, vWF - Blood gas (iStat Alinity): pH, pCO2, pO2, HCO3, BE, SO2, iCa, lactate, anion gap Add 38 new clinical patterns in alteraciones.json with full acid-base interpretation logic (respiratory/metabolic acidosis and alkalosis), DIC detection, coagulopathy classification, cardiac biomarkers, acute phase response, drug monitoring thresholds and urinary sediment patterns. Add Factores de Coagulación and Gasometría collapsible panels to the UI. Update CSS grid (4 rows) to place new panels between Endo/Uri and Flujo. Fix .htaccess: switch JS/CSS/JSON to no-cache for instant dev reloads. Co-Authore…a50e2a1
-
Add Spanish explanatory comments across JS modules and PHP proxy
josesalazar2025d90e9a6 -
Add Docker usage section to README and include index.html updates
josesalazar202506f94a3 -
Refactor panel headers: h2 as first section child, panel-cabecera as div
josesalazar2025Add visually-hidden h2.panel-titulo as first direct child of each section for semantic/SEO correctness. Change panel-cabecera from h2 to div keeping all visual content and layout unchanged.0431806 -
Update CSS from IDE edits and bump cache version to v=3
josesalazar202518c7aa5 -
Fix mobile lab input layout: fixed-width estado-campo and narrower inputs
josesalazar2025Reserve 5.5rem for estado-campo flags on all lab panels so inputs never shift position when Alto/Bajo flags appear. Reduce input width to 3.5rem (fits 4 digits). Apply same rules to panel-uri and panel-endo.cfed467 -
Add cache-busting query params and restore 1-year CSS/JS cache policy
josesalazar2025Use ?v=2 on CSS and JS links so browsers fetch fresh files after this deploy. Revert .htaccess to 1-year immutable cache to keep Lighthouse score intact. Bump v= on future deploys that change CSS or JS.084212f -
Fix modal input overflow, papers modal mobile height, and default panel states
josesalazar2025- Add width/box-sizing to .modal-campo input to prevent apellido field overflow at wide resolutions - Anchor papers modal to top on mobile using svh units so header and paginators stay visible - Collapse panel-uri and panel-endo by default on first visit; panel-flujo stays expanded15c6ffb -
Add <unused95> token to force skip reasoning phase + console debugging for desktop vs mobile comparison
Jose Salazard8aaa23 -
Resize uploaded images to 1024px max to prevent mobile network errors from large payloads
Jose Salazar1441be7 -
Simplify image prompt: remove patterns, only abnormal values, put image question at end for recency bias
Jose Salazarc51b22a -
Prioritize image analysis in prompt when cytology images are attached
Jose Salazar4486d90 -
Update prompt: include all values with reference ranges and detected patterns
Jose Salazar3a64e85 -
Refactor AI prompt from negative constraints to positive example-based format
Jose SalazarReplaces prohibitive instructions (Prohibido, INCUMPLIR) with a positive prompt engineering approach using: - Clear role definition - Simple positive rules - Concrete example of expected output format This reduces the likelihood of the model outputting reasoning processes in English, as negative constraints often trigger the opposite behavior in instruction-tuned models.42cdd91 -
Send only abnormal findings and strengthen anti-reasoning prompt
Jose Salazarc943c9a -
Prevent model reasoning output and add reasoning detection/cleanup
Jose Salazarb819476 -
Add CORS headers and OPTIONS preflight support for mobile browsers
Jose Salazar9869459 -
Add git for HF Spaces build process
Jose Salazar8d62a29