0 puntos seleccionados
Enviar por WhatsApp
Auditoria interna · Mayo 2026

Auditoria de calidad
Obsidiana App

Revise toda la app a profundidad: codigo, servicios, pagos, PWA, UX. Separe todo en lo que SI hay que arreglar (bugs reales) y lo que podria mejorar (consideraciones del equipo).

12
Criticos
18
Importantes
20
Consideraciones
Como usar esta pagina: Lean cada punto con su equipo. Si creen que vale la pena trabajarlo, marquen el checkbox. Al final pueden enviar todo lo seleccionado por WhatsApp para discutirlo.
scroll
Seccion 1 — Bugs criticos

Cosas que estan rotas y hay que arreglar

Estos son defectos verificados en el codigo. Afectan directamente a los usuarios, la seguridad, o impiden que la app funcione correctamente. No son opiniones.

Build y PWA

Build roto por favicon fantasma

vite.config.ts referencia favicon/favicon.svg en includeAssets, pero ese archivo no existe en public/. Ademas pesaria 8MB, excediendo el limite de Workbox. Resultado: el Service Worker nunca se genera y la PWA no tiene offline.

vite.config.ts:18 · build_output.txt:58-72
CriticoBuild

Tailwind CDN rompe la app offline

El <script src="https://cdn.tailwindcss.com"> carga el compilador JIT en runtime (~400KB). Tailwind documenta que el Play CDN es solo para desarrollo. El Service Worker no cachea scripts externos. Resultado: sin internet = app completamente sin estilos.

index.html:58
CriticoPWA

Bundle principal demasiado grande (951KB)

El JS principal pesa 951KB minificado. En moviles con 3G esto son 5-15 segundos de pantalla blanca. La dependencia firebase (~400KB) esta en package.json pero ningun archivo de la app lo importa — solo se usa en functions/.

build_output.txt:27 · package.json:13
CriticoPerformance
Seguridad

API Key de Groq visible para cualquier usuario

La key se carga con import.meta.env.VITE_GROQ_API_KEY. Cualquier variable VITE_ se embebe en el bundle del browser. El dangerouslyAllowBrowser: true lo confirma. Cualquiera puede abrir DevTools, extraer la key, y hacer llamadas ilimitadas a tu cuenta.

services/aiService.ts:6-7, 22-24
CriticoSeguridad

XSS: HTML sin sanitizar del Glossary

dangerouslySetInnerHTML={{ __html: wiki.snippet }} renderiza HTML crudo de la API de Wikipedia directamente en el DOM. Un atacante que manipule la respuesta o un snippet inesperado podria inyectar scripts.

components/Glossary.tsx:184
CriticoSeguridad

Conversaciones privadas se filtran entre usuarios

chatHistory es una variable de modulo que persiste entre sesiones. Si usuario A cierra sesion y B inicia, B recibe el contexto de la conversacion de A (contenido de terapia/espiritual). Ademas, localStorage (obsidiana_dreams, obsidiana_bitacoras, obsidiana_agenda, etc.) NO se limpia en logout.

services/aiService.ts:121 · context/AppContext.tsx:256-267
CriticoPrivacidad
Errores fatales de React

ErrorBoundary no funciona (pantalla blanca garantizada)

Es un componente funcional con useState. React requiere un class component con getDerivedStateFromError() para capturar errores de render. Si cualquier hijo crashea, la app entera muestra pantalla blanca. Ademas, el ErrorFallback llama useApp() que depende de AppProvider — si el error viene de ahi, el fallback tambien crashea (doble crash).

components/ErrorBoundary.tsx:16-50 · index.tsx:146-154
CriticoEstabilidad

Loop infinito en la pantalla de Mensajes

El useEffect depende de [user.name, loading]. Dentro llama setLoading(false), que cambia loading, que re-ejecuta el effect, que vuelve a llamar loadMessages()... Loop infinito que hace fetch a Supabase cientos de veces por segundo y re-suscribe canales de realtime en cada ciclo.

components/Messages.tsx:112
CriticoMessages

Mensajes en orden aleatorio

Los timestamps se formatean a strings como "2 may 2026, 10:30" y luego se intenta new Date("2 may 2026, 10:30").getTime() que retorna NaN en la mayoria de browsers. Ordenar con NaN produce orden inestable/aleatorio.

components/Messages.tsx:76-78, 156
CriticoMessages
Pagos y Stripe

Premium es permanente (no hay cancelacion)

Ningun webhook handler escucha customer.subscription.deleted, subscription.updated, ni invoice.payment_failed. Una vez que is_premium = true, nunca regresa a false. Usuarios que cancelan o cuyo pago falla mantienen premium para siempre.

supabase/functions/stripe-webhook/index.ts · functions/src/index.ts
CriticoPagos

Dos sistemas de pago sin sincronizar

El handler de Firebase escribe en Firestore, el de Supabase escribe en la tabla profiles. Si Stripe apunta a uno solo, el otro nunca se actualiza. Ademas, el handler de Firebase da premium por defecto a cualquier checkout que no reconozca (isProPurchase = true como fallback).

functions/src/index.ts:104-113 · supabase/functions/stripe-webhook/index.ts
CriticoPagos

Pagos perdidos silenciosamente

Cuando el webhook no encuentra al usuario (ni por client_reference_id ni por email), retorna status 200. Stripe interpreta esto como exito y no reintenta. El usuario pago pero su cuenta nunca se actualiza a premium.

supabase/functions/stripe-webhook/index.ts:58-64, 181-186
CriticoPagos

Seccion 2 — Bugs importantes

Bugs que afectan la experiencia

No van a tumbar la app, pero los usuarios los van a notar. Causan datos incorrectos, UI rota, o comportamiento inesperado.

Datos incorrectos

Agenda: todas las fechas muestran +1 dia

new Date(event.date).getDate() + 1 — un evento guardado para el 15 se muestra como el 16. Todos los eventos del calendario estan desfasados un dia.

components/Agenda.tsx:232
Bug importanteAgenda

Fase lunar incorrecta para fechas historicas

El operador % de JavaScript preserva el signo negativo. Para cualquier fecha antes de enero 2023, phaseCycle es negativo, produciendo un phaseIndex negativo que siempre cae en "Luna Nueva" sin importar la fase real.

utils/moonUtils.tsx:83
Bug importanteDashboard

Edad calculada con error de +/- 1 ano

getFullYear() - getFullYear() no considera si el cumpleanos ya paso este ano. Alguien nacido el 31 de diciembre se muestra un ano mayor. Afecta cyclesRemaining (ciclos fertiles) y la validacion de edad minima (menores podrian pasar).

components/Dashboard.tsx:446 · utils/validation.ts:41-44
Bug importanteDashboard

Mensajes identificados por nombre, no por ID

Todos los queries de mensajes usan user.name (nombre visible) como identificador. Si dos usuarios tienen el mismo nombre, sus mensajes se mezclan. Si alguien cambia su nombre, pierde acceso a todas sus conversaciones anteriores.

components/Messages.tsx:38-43, 67
Bug importanteMessages

Fecha de periodo no se guarda en el servidor

handleUpdatePeriodStart actualiza el state local pero nunca escribe a la tabla profiles de Supabase. Si la usuaria cierra sesion antes de ir a "Editar Perfil", el dato se pierde del lado del servidor.

components/Dashboard.tsx:397
Bug importanteDashboard
UI rota

Trial: mutacion directa del prop user

user.trialStartTime = now muta el objeto directamente en vez de usar setUser({...user, trialStartTime: now}). React no detecta el cambio, los componentes que dependen de trialStartTime no se re-renderizan, y el trial timer puede no funcionar correctamente.

components/Layout.tsx:57
Bug importanteTrial

Perfil muestra exito falso cuando falla el guardado

setIsSaved(true) se ejecuta fuera del try, asi que siempre muestra "Sincronizacion sagrada completada" aunque la escritura a Supabase haya fallado. La usuaria cree que sus datos se guardaron cuando no fue asi.

components/UserProfileEdit.tsx:64-93
Bug importantePerfil

Perfil sin validacion de datos

validateUserProfile existe y se usa en Login, pero UserProfileEdit NO lo importa. Se pueden guardar nombres vacios, fechas de nacimiento futuras, o ciclos de 0 dias.

components/UserProfileEdit.tsx · utils/validation.ts
Bug importantePerfil

Auto-login roto para usuarios que regresan

Cuando un usuario con perfil completo vuelve a la app, loadProfileAndCheck nunca llama onLogin. En vez de entrar directo al Dashboard, ve el formulario de perfil otra vez.

components/Login.tsx:31-37
Bug importanteLogin

Botones de pago se congelan permanentemente

setIsLoading(true) se activa antes del redirect a Stripe, pero si el redirect falla (popup bloqueado, back del browser), isLoading nunca se resetea. Todos los botones quedan deshabilitados mostrando "Redirigiendo..." para siempre.

components/PremiumUnlock.tsx:23-33 · components/ProUpgrade.tsx:22-32
Bug importantePagos
Race conditions

Community: likes divergen entre local y servidor

handleReaction hace optimistic update local y luego un write a Supabase, pero el write lee el valor viejo del state. El servidor y el cliente terminan con conteos diferentes. Lo mismo pasa con los comentarios.

components/Community.tsx:149-188, 236-258
Bug importanteCommunity

Edicion de perfil se revierte al subir imagen

setFormData({...formData, avatarUrl}) cierra sobre el formData de cuando empezo el upload. Si la usuaria edita su nombre mientras la imagen sube, el nombre vuelve al valor anterior cuando el upload termina.

components/UserProfileEdit.tsx:50-54
Bug importantePerfil
Errores silenciosos

Errores de AI invisibles (3 secciones)

Cuando la AI falla, el error solo va a console.error. La usuaria no ve ningun mensaje, ninguna opcion de reintentar. En el Chatbot esto es especialmente grave: escribe algo personal/emocional y simplemente no recibe nada.

components/Chatbot.tsx:51-53 · DreamJournal.tsx:70-73 · Bitacoras.tsx:74-77
Bug importanteAI

Dashboard crashea con localStorage corrupto

JSON.parse(saved) se llama sin try/catch. Si los datos de localStorage estan corruptos (raro pero posible), todo el Dashboard crashea con una excepcion al montar.

components/Dashboard.tsx:26 · DreamJournal.tsx:13 · Bitacoras.tsx:13
Bug importanteDashboard
Configuracion

Typo en OAuth: 'consensus' en vez de 'consent'

prompt: 'consensus' no es un valor valido de Google OAuth. Los valores correctos son none, consent, select_account. Google ignora el parametro invalido.

src/components/LoginSupabase.tsx:45
Bug importanteAuth

Agenda: export iCal roto a las 23:00

h + 1 para hora 23 produce DTEND:T240000 que no es valido en iCal. Algunos calendarios rechazan el archivo completo.

components/Agenda.tsx:70-71
Bug importanteAgenda

Dos manifests PWA contradiciendose

site.webmanifest dice nombre "Osiris", tema dorado (#e8bf1a). VitePWA genera uno con "Obsidiana", tema rosa (#831843). Iconos apuntan a paths diferentes. Puede causar iconos rotos o colores incorrectos en la pantalla de splash.

public/favicon/site.webmanifest · vite.config.ts:22-44
Bug importantePWA

Link de Stripe TEST en produccion

La URL de "Membresia Premium" contiene /test_ indicando modo test de Stripe. Los links de libro y donacion son live. La usuaria paga en modo test y no recibe nada.

components/PremiumUnlock.tsx:28
Bug importantePagos

Seccion 3 — Consideraciones

Cosas que podrian mejorar

Estas no son cosas rotas — son oportunidades que encontramos y que, en nuestra experiencia, pueden elevar la calidad de la app. El equipo decide cuales valen la pena.

Nota importante: Estas son sugerencias basadas en lo que vimos en la auditoria del codigo. Ninguna es obligatoria, pero cada una resolveria una friccion real que las usuarias podrian experimentar. Marquen las que les parezcan valiosas.
Experiencia del Chatbot

Chatbot pierde la conversacion al navegar

Los mensajes solo estan en React state. Si la usuaria navega a otra seccion y vuelve, toda la conversacion desaparece. Para una feature con tono terapeutico/espiritual, perder la continuidad rompe la experiencia.

ConsideracionChatbot

Chatbot sin contexto multi-turno

sendMessageToOsiris(input) manda solo el ultimo mensaje. La AI no tiene contexto de lo que se discutio antes. Las conversaciones de varios mensajes suenan desconectadas porque la AI no recuerda nada.

ConsideracionChatbot
Gating de premium

Pregunta Milagro no esta gated por premium

handleSubmitMiracle llama a la AI sin verificar premium ni trial. Dreams, Bitacoras y Chatbot si tienen gate. Esto significa que usuarios gratis pueden consumir tokens de AI ilimitadamente desde el Dashboard.

ConsideracionDashboard

Componente muerto con precios diferentes

PremiumUnlock.tsx nunca se renderiza pero sigue en el repo con precios distintos: libro $49.99 (vs $5.99 en BookLibrary), premium $19.99/mes (vs $9.99 en ProUpgrade). Si alguien lo importa por error, las usuarias ven precios incorrectos.

ConsideracionPagos
Accesibilidad

Zoom deshabilitado en viewport

maximum-scale=1.0, user-scalable=no impide que las usuarias hagan pinch-to-zoom. Esto viola WCAG 2.1 (Criterio 1.4.4). Ya existe touch-action: manipulation en CSS que previene doble-tap zoom sin bloquear pinch.

ConsideracionAccesibilidad

No se puede copiar texto en toda la app

user-select: none en el body impide seleccionar y copiar cualquier texto (excepto inputs). Las usuarias no pueden copiar respuestas del chatbot, definiciones del glosario, ni interpretaciones de suenos. Recomendamos aplicarlo solo a botones y navegacion.

ConsideracionAccesibilidad

Botones sin texto accesible

El hamburger menu, boton de enviar chat, boton de perfil, botones de eliminar en Agenda... todos son iconos sin aria-label. Lectores de pantalla solo dicen "boton" sin descripcion.

ConsideracionAccesibilidad
Internacionalizacion

i18n existe pero es dead code

translations.ts tiene traducciones completas en ingles y espanol, pero ninguna se usa excepto una linea en ErrorBoundary. Todos los componentes tienen strings hardcoded en espanol. No hay language switcher ni provider. Si la app solo sera en espanol, recomendamos eliminar el archivo para no confundir.

Consideracioni18n
Glossary

Iframe de Wikipedia siempre en blanco

Wikipedia envia X-Frame-Options: DENY. El boton "Leer en Wikipedia sin salir de la app" muestra un rectangulo blanco para siempre. No hay deteccion del error ni link alternativo.

ConsideracionGlossary

Busqueda rota con caracteres especiales

srsearch=${searchTerm} sin encodeURIComponent(). Buscar "VPH & cancer" o "utero #2" rompe el URL silenciosamente y da resultados vacios o incorrectos.

ConsideracionGlossary

Modal no se cierra al tocar afuera

El modal de detalle del Glossary no tiene onClick en el backdrop. Las usuarias esperan poder cerrarlo tocando fuera del modal, pero solo funciona el boton X.

ConsideracionGlossary
Agenda

Recordatorios son decorativos

Se pide permiso de notificaciones y se guarda reminderEnabled: true, pero no existe logica de scheduling. La usuaria habilita recordatorios para un evento esperando que le avisen, pero el reminder nunca llega.

ConsideracionAgenda
Navegacion y layout

Biblioteca sin navegacion visible

BookLibrary renderiza fuera del Layout, asi que no hay sidebar, header, ni navegacion. La unica salida es el boton "cerrar". Si ese boton falla o esta oculto en pantalla completa, la usuaria queda atrapada.

ConsideracionNavegacion

Logout con nombres diferentes por plataforma

El sidebar de desktop dice "Cerrar Sesion" y el menu mobile dice "Finalizar Sesion". Son la misma accion con nombres diferentes, lo cual puede confundir a usuarias que usan ambos.

ConsideracionLayout
Performance y estabilidad

Sin timeout en llamadas a la AI

Las funciones callGroq y sendMessageToOsiris no tienen AbortController ni timeout. Si la API de Groq tarda o se cuelga, el spinner gira para siempre sin opcion de cancelar.

ConsideracionAI

onKeyPress deprecado (4 componentes)

onKeyPress esta deprecado en React y no funciona correctamente en algunos browsers mobile. Afecta Chatbot, DreamJournal, Bitacoras, y Community. Deberia ser onKeyDown.

ConsideracionInput

NavItem se re-crea en cada render

NavItem esta definido como componente dentro del body de Layout. React lo re-crea en cada render, causando unmount/remount de todos los items de navegacion y perdiendo cualquier estado interno.

ConsideracionPerformance
Instalacion PWA

Banner de instalacion reaparece en cada recarga

InstallBanner guarda el dismiss en useState (memoria de sesion). Cada vez que la usuaria recarga la pagina, el banner vuelve a aparecer. El prompt de iOS si usa localStorage. Comportamiento inconsistente.

ConsideracionPWA

iPads no ven el prompt de instalacion

La deteccion de iOS usa /iPad|iPhone|iPod/.test(navigator.userAgent) pero iPads con iPadOS 13+ reportan user agent de desktop Safari. Las usuarias de iPad nunca ven las instrucciones de instalacion.

ConsideracionPWA
Miscelaneo

README completamente desactualizado

El README habla de "AI Studio", tiene link a ai.studio/apps, e instruye configurar GEMINI_API_KEY. La app usa Groq + Supabase. Cualquier developer nuevo que lea el repo se confunde.

ConsideracionDocs

Enviar seleccion al equipo

Revisen lo que seleccionaron. El boton genera un mensaje con todos los puntos marcados.