supabase/migrations/20261014000005_rendimiento_seguridad_privacidad.sql y pulsa Run. No borra datos de nadie
(solo el esquema temporal tmp_imagenes, que sobró de la carga de fotos del catálogo y ya está en seed_imagenes.sql).
Yo no pude aplicarla: el permiso para cambiar tu base de datos en la nube fue rechazado las dos veces que lo intenté,
así que todo se probó en una copia local con las mismas migraciones.
Sin este paso la API nueva no puede identificar (le faltan las funciones nuevas); python -m scripts.diagnostico te lo avisa.http://localhost:4200/** si no está (el correo que confirma
la cuenta de un invitado lleva a /nueva-contrasena para elegir su contraseña).Opcional: hazte administrador para usar el panel de revisión (ver COMO_EJECUTAR.md).
Medido con Claude y GBIF reales (5 fotos, base de datos local; con Supabase en la nube súmale unas décimas):
| Antes | Ahora | |
|---|---|---|
| Nombre de la especie visible | al final (~10 s en la app) | 3.2 s (mediana) |
| Resultado guardado | ~10 s | 4.4 s (mediana) |
Qué cambió:
Precisión (sin perderla): se midió con python -m scripts.evaluar_ia y 33 fotos reales de iNaturalist de especies conocidas.
Antes: 68 % de aciertos de especie (promedio de 2 corridas); ahora: 68 % (promedio de 3). La especie correcta aparece entre las
parecidas en el 84 % de los casos (antes 78 %), justo lo que aprovecha “¿Cuál de estas es?”. También se probó responder sin
razonar: casi el doble de rápido, pero acertaba 54-58 %, así que se descartó (IA_RAZONAMIENTO=bajo, el valor por defecto).
Gasto total en IA de todo este trabajo (evaluaciones y pruebas reales): unos 0.13 dólares.
uso_ia, que la app no puede leer ni borrar, y la API reserva
el lugar antes de llamar a la IA, con un candado por persona. Si la IA falla, el lugar se devuelve.ajustes de Supabase (antes estaban repetidos en tres lugares).observaciones_publicas, que Supabase marcaba
como ERROR de seguridad, se cambió por la función avistamientos_publicos.PRODUCCION=1) la persona no ve detalles técnicos de los errores; el Dockerfile corre sin privilegios.Sistema de diseño propio (“guía de campo moderna”): verde musgo y maíz, tipografía Fraunces (títulos y nombres científicos en cursiva) y Manrope, un color por grupo, modo oscuro (automático o elegido) y fuentes incluidas en la app (no dependen de internet).
app/android) con permisos de cámara, galería y ubicación.supabase/migrations/20261014000005_rendimiento_seguridad_privacidad.sql (nueva) y supabase/tests/*.app/ia.py, app/main.py, app/config.py, app/supabase_client.py y app/fotos_especies.py; nuevos app/identificacion.py,
app/sesion.py, app/taxonomia.py, scripts/evaluar_ia.py y scripts/evaluacion/; además scripts/diagnostico.py,
scripts/prueba_real.py, requirements.txt, Dockerfile, .env.example y todas las pruebas.src/app/**, src/styles/, src/index.html, public/), angular.json, package.json y android/.pruebas/e2e/ (ahora corren en Windows con levantar.py); se borró levantar.sh.supabase/tests/01_pruebas_rls.sql (límite, invitados, red, tope global, privacidad, colección, revisión).Dockerfile) y poner su dirección en app/src/environments/environment.prod.ts
(apiUrl). La app nativa la necesita: en el teléfono no existe “localhost”.app/android con Android Studio, con JDK 21 (el JDK 25 que trae tu Android Studio puede no funcionar con el
Gradle de Capacitor 8). Para iOS hace falta una Mac (npx cap add ios).Probé tu clave directamente contra Claude. La respuesta real fue: “This API key is not scoped to a workspace, so this request
must include the anthropic-workspace-id header”. Tu clave era de ámbito “Organización”, y ese tipo de clave no sirve
sola. La API solo mostraba “Claude rechazó la petición” y escondía el motivo. Antes de eso hubo otros dos problemas:
en api/.env estaba el identificador de la clave (apikey_…) y después la clave abreviada que muestra la consola (con “…”).
Solución: una clave nueva del espacio de trabajo “Default” (ver COMO_EJECUTAR.md, paso 3b). Si prefieres conservar
una clave de Organización, la API ahora acepta ANTHROPIC_WORKSPACE_ID en api/.env.
api/.env más tolerante. Funciona aunque el Bloc de notas lo guarde con BOM o la clave quede entre comillas.
Si IA_MODELO sigue diciendo gemini-…, se usa claude-haiku-5-5.python -m scripts.diagnostico dice con qué empieza y cuánto mide la clave que está leyendo
(sin mostrarla) y hace una identificación real con Claude.api/app/main.py, y se volvió a agregar api/.env.example, ahora solo con Claude..gitattributes: los scripts .sh se guardan con saltos de línea de Linux (con los de Windows no corrían).api/app/ia.py, api/app/config.py, api/app/main.py, api/.env.example (nuevo), api/scripts/diagnostico.py,
api/scripts/prueba_real.py, api/tests/conftest.py, api/tests/test_ia.py, api/tests/test_api.py,
app/src/app/core/api.service.ts, app/src/app/core/api.service.spec.ts, pruebas/e2e/fake_ia.py, pruebas/e2e/levantar.sh,
pruebas/e2e/prueba_e2e.py, .gitattributes (nuevo), COMO_EJECUTAR.md, README.md, CAMBIOS.md.
No cambia nada en la base de datos.
python -m scripts.diagnostico C:\ruta\a\foto.jpg.En los registros de Supabase se veía lo mismo en cada intento: la API validaba la sesión, revisaba el límite y descargaba la foto, pero nunca guardaba el resultado. La falla estaba en Gemini: en varios intentos tardó más de los 45 s que la API esperaba (la app borraba la foto unos 46 s después), y en otros lo rechazó al instante. Nunca se había guardado ni una observación.
IA_MODELO no existe, se quedó sin cuota (la cuota gratuita es por modelo),
da error o tarda más de 40 s, la API prueba sola gemini-flash-latest, gemini-flash-lite-latest y gemini-2.5-flash
(configurable con IA_MODELOS_RESPALDO). Si la clave es inválida, no pierde tiempo probando otros.IA_TIEMPO_MAXIMO) y 110 s en total (IA_TIEMPO_TOTAL). La app espera hasta 150 s.thinkingLevel: low en Gemini 3 y thinkingBudget: 0 en Gemini 2.5.python -m scripts.diagnostico revisa Supabase, el bucket, la clave de Gemini y los modelos, y hace una identificación de prueba.
python -m scripts.prueba_real foto.jpg hace el recorrido completo de la app con un usuario temporal y lo borra al final.api/app/ia (1).py (copia vieja), api/app/puebla.py, api/app/data/puebla.geojson y api/tests/test_puebla.py,
que la versión anterior decía haber eliminado. Se agregó api/.env.example, que la guía mencionaba pero no existía.api/.env. Se agregaron pruebas de los modelos de respaldo.POST /identificar
→ Guacamaya verde (Ara militaris), confianza 0.95, en 20.6 s → observación guardada y legible por la app. Todo lo de la prueba se borró al final.gemini-2.5-flash ya no está disponible para claves nuevas (corregido el mismo día: ahora se usa gemini-3.8-flash).Velocidad
thinkingLevel: low). Si el modelo no acepta ese ajuste, la API repite sola sin él.IA_TIEMPO_MAXIMO) y la app 2 minutos. Después avisa en vez de quedarse cargando.Especies de cualquier lugar, sin guardar de más (“catálogo que crece solo”)
verificada = false) para revisarla después.
Solo se agrega con confianza de 0.5 o más, para no llenar el catálogo de errores. La foto de referencia se busca en Wikipedia
en segundo plano, sin retrasar la respuesta.verificada = true).Pantallas
Base de datos: migración supabase/migrations/20261013000004_catalogo_abierto.sql, ya aplicada en tu Supabase.
Solo agrega la columna especies.verificada y no borra nada.
Se eliminaron (ya no se usan): api/app/puebla.py, api/app/data/puebla.geojson, api/tests/test_puebla.py.
Pruebas: API 31 de 31, app 14 de 14, base de datos completa y navegador 21 de 21. Las pruebas del navegador incluyen una especie nueva fotografiada en la CDMX que se agrega sola al catálogo. Sin probar todavía: con tu clave real de Gemini y la búsqueda de fotos en Wikipedia, porque desde aquí no hay acceso a esos servicios.
Causa real: la foto sí se subía a Supabase (se ve en los registros de Storage a las 22:06). Lo que fallaba era el paso siguiente: la API de Python
no tenía su configuración (archivo api/.env ausente o incompleto). Al intentar hablar con Supabase con una URL vacía se caía
con un error 500 sin los encabezados CORS. Por eso el navegador no podía leer la respuesta y la app lo mostraba como “sin internet”.
Corrección:
Migración supabase/migrations/20261012000003_base_en_espanol.sql, ya aplicada en tu proyecto de Supabase.
Solo renombra objetos, no borra nada. Se conservaron tu perfil, las 95 especies y todo lo que ya existía.
| Antes | Ahora |
|---|---|
profiles (username, role, created_at) |
perfiles (nombre_usuario, rol, creado_en). Valores de rol: usuario / admin |
species (common_name, scientific_name, kingdom, is_protected, …) |
especies (nombre_comun, nombre_cientifico, reino, protegida, categoria_nom059, introducida, familia, descripcion, imagen_url, imagen_fuente nueva, …) |
observations (user_id, photo_path, species_id, ai_confidence, status, is_public, …) |
observaciones (usuario_id, ruta_foto, especie_id, ia_confianza, estado, es_publica, …) |
vista public_observations |
vista observaciones_publicas |
funciones is_admin, handle_new_user, username_disponible |
es_admin, crear_perfil, nombre_usuario_disponible |
También se renombraron índices, restricciones y disparadores. La app, la API, el catálogo, las pruebas y la guía usan los nombres nuevos.
Causa: ninguna especie del catálogo tenía foto (imagen_url vacía), y la app no tenía qué mostrar cuando una imagen fallaba.
Corrección:
especies.imagen_fuente y se muestra en la ficha (“Foto de referencia: Wikimedia Commons”).supabase/seed_imagenes.sql (ya aplicado; sirve para un proyecto nuevo).app-foto (app/src/app/shared/foto.component.ts), usado por todas las imágenes de la app. Tiene una caja de tamaño fijo y recorta
la foto sin deformarla (object-fit: cover). Muestra un fondo con icono mientras carga y una transición suave al aparecer.WARNING: Fallo al identificar con Gemini: …). Archivos: api/app/ia.py, api/app/main.py.Modelo de Gemini: gemini-2.5-flash ya no está disponible para claves nuevas. El modelo por defecto ahora es gemini-3.8-flash (plan gratuito). Archivos: api/app/config.py, api/.env.example.
API (Python): api/app/main.py, api/app/config.py, api/app/supabase_client.py, api/app/ia.py, api/.env.example, api/requirements.txt,
api/scripts/catalogo_gbif.py, api/tests/conftest.py, api/tests/test_api.py, api/tests/test_ia.py, api/tests/test_catalogo_gbif.py
App (Angular/Ionic):
core/modelos.ts, core/api.service.ts, core/api.service.spec.ts, core/fotos.service.ts, core/fotos.service.spec.ts,
core/supabase.service.ts, core/observaciones.service.ts, core/catalogo.service.ts, core/grupos.ts, core/grupos.spec.ts, core/ubicacion.service.tsidentificar/inicio.page.*, identificar/analizando.page.*, especies/catalogo.page.*, especies/ficha.page.*,
observaciones/historial.page.*, observaciones/observacion.page.*, perfil/perfil.page.*shared/foto.component.ts y shared/foto.component.spec.ts (nuevos)src/styles.scss, src/environments/environment.ts, src/environments/environment.prod.ts, package.json (script start:red)Base de datos: supabase/migrations/20261012000003_base_en_espanol.sql (nuevo), supabase/seed_imagenes.sql (nuevo), supabase/seed.sql,
supabase/tests/01_pruebas_rls.sql
Pruebas y documentación: pruebas/e2e/levantar.sh, pruebas/e2e/prueba_e2e.py, pruebas/e2e/fake_ia.py, COMO_EJECUTAR.md, README.md, CAMBIOS.md
Sin probar todavía: la app conectada a tu Supabase real con Gemini real. Desde aquí no hay acceso de red a Supabase, así que esa prueba la haces tú.