Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .env
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
EXPO_PUBLIC_FIREBASE_API_KEY=AIzaSyBAyoohTHVBz3bZFmRiDb6s80D2hBeMSfs
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=visuwallet.firebaseapp.com
EXPO_PUBLIC_FIREBASE_DATABASE_URL=https://visuwallet-default-rtdb.firebaseio.com
EXPO_PUBLIC_FIREBASE_PROJECT_ID=visuwallet
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=visuwallet.firebasestorage.app
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=410749935369
EXPO_PUBLIC_FIREBASE_APP_ID=1:410749935369:web:5d6e1ac0ff306ea26cfdd8
EXPO_PUBLIC_FIREBASE_MEASUREMENT_ID=G-KFVKWGVXD2
EXPO_PUBLIC_FIREBASE_API_KEY=AIzaSyCizNbDECCLXJyBimg1Q2UFalrguFdQ614
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=pre-taws.firebaseapp.com
EXPO_PUBLIC_FIREBASE_DATABASE_URL=https://pre-taws-default-rtdb.firebaseio.com
EXPO_PUBLIC_FIREBASE_PROJECT_ID=pre-taws
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=pre-taws.firebasestorage.app
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=787398838709
EXPO_PUBLIC_FIREBASE_APP_ID=1:787398838709:web:1e6ebc76530cfa47b9b1fc
EXPO_PUBLIC_FIREBASE_MEASUREMENT_ID=G-51P1KVVG6H
51 changes: 27 additions & 24 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
// App.tsx
// App.tsx

import 'global.css';
import { useState } from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';

import Home from 'components/Home';
import Inicio from 'components/Inicio';
import Formulario from 'components/Formulario';
import DetalleCuenta from 'components/DetalleCuenta';
import Estadisticas from 'components/Estadisticas';
import ChartsScreen from 'components/ChartsScreen';
import Perfil from 'components/Perfil';
import Login from 'components/Auth/Login';
import Signup from 'components/Auth/Signup';
import "global.css";
import { useState } from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";

import Home from "components/Home";
import Inicio from "components/Inicio";
import Formulario from "components/Formulario";
import DetalleCuenta from "components/DetalleCuenta";
import Estadisticas from "components/Estadisticas";
import ChartsScreen from "components/ChartsScreen";

import { Cuenta } from './types';

export default function App() {
function AppContent() {
// Estado para controlar qué pantalla mostrar
const [currentScreen, setCurrentScreen] = useState<
'login' | 'signup' | 'home' | 'inicio' | 'formulario' | 'detalleCuenta' | 'estadisticas' | 'charts' | 'perfil'
>('login');
const [currentScreen, setCurrentScreen] = useState<'home' | 'inicio' | 'formulario' | 'detalleCuenta' | 'estadisticas' | 'charts'>('home');

// Estado para guardar la cuenta seleccionada
const [selectedAccount, setSelectedAccount] = useState<Cuenta | null>(null);
Expand All @@ -39,8 +34,18 @@ export default function App() {
setCurrentScreen('detalleCuenta');
};

if (isLoading) {
return (
<SafeAreaProvider>
{/* loading state */}
</SafeAreaProvider>
);
}

const isLoggedIn = !!user;

return (
<SafeAreaProvider>
<SafeAreaProvider>
{/* Renderizar la pantalla según el estado */}

{currentScreen === 'login' && (
Expand All @@ -51,7 +56,9 @@ export default function App() {
<Signup onSignupSuccess={() => navigateTo('home')} onBack={() => navigateTo('login')} />
)}

{currentScreen === 'home' && (
{!isLoggedIn ? (
<Login onContinue={() => setCurrentScreen('home')} />
) : currentScreen === 'home' && (
<Home
onPressAdd={() => navigateTo('inicio')}
onPressAccount={navigateToAccountDetail}
Expand Down Expand Up @@ -90,10 +97,6 @@ export default function App() {
onPressPerfil={() => navigateTo('perfil')}
/>
)}

{currentScreen === 'perfil' && (
<Perfil onBack={() => navigateTo('home')} />
)}
</SafeAreaProvider>
);
}
}
61 changes: 61 additions & 0 deletions README.firestoredb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Firestore Data Model for VisuWallet

## Colecciones y documentos

- `usuarios/{uid}`
- `nombre`: string
- `telefono`: string
- `providerId`: string (opcional)
- `createdAt`, `updatedAt`: timestamp
- Subcolección: `cuentas/{cuentaId}`
- `nombre`: string
- `numero`: string
- `tipo`: string
- `moneda`: string
- `saldoInicial`: number
- `notas`: string (opcional)
- `ownerUid`: string (igual a `uid`)
- `createdAt`, `updatedAt`: timestamp

- `transacciones/{txId}`
- Datos del formulario:
- `monto`: number
- `tipo`: string
- `categoria`: string
- `fecha`: timestamp
- `descripcion`: string (opcional)
- Referencias:
- `refUsuario`: reference → `usuarios/{uid}`
- `refCuenta`: reference → `usuarios/{uid}/cuentas/{cuentaId}`
- Campos denormalizados para consultas:
- `ownerUid`: string
- `cuentaId`: string
- `cuentaNumero`: string (opcional)
- Auditoría:
- `createdAt`, `updatedAt`: timestamp
- `createdByUid`: string

## Consultas recomendadas

- Por usuario: `where('ownerUid','==',uid).orderBy('fecha','desc')`
- Por cuenta: `where('ownerUid','==',uid).where('cuentaId','==',cuentaId).orderBy('fecha','desc')`
- Por rango de fechas: `where('ownerUid','==',uid).where('fecha','>=',start).where('fecha','<=',end).orderBy('fecha')`

## Índices sugeridos

Ver `firestore.indexes.json` incluido en el proyecto.

## Reglas de seguridad

Ver `firestore.rules` incluido en el proyecto. En resumen:

- `usuarios` y sus `cuentas` solo son accesibles por su dueño (`request.auth.uid`).
- `transacciones` solo pueden ser leídas/escritas por el dueño (`ownerUid`).
- En `create` de `transacciones` se valida que las referencias apunten al usuario y cuenta correctos.

## Buenas prácticas

- Usar subcolecciones para listas que crecen (cuentas por usuario).
- Mantener denormalización mínima en `transacciones` (`ownerUid`, `cuentaId`, `cuentaNumero`) para consultas rápidas.
- Acompañar referencias (`refUsuario`, `refCuenta`) con IDs planos para filtros.
- Añadir timestamps con `serverTimestamp()` y auditar con `createdByUid`.
19 changes: 15 additions & 4 deletions components/AddCuenta.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState } from 'react';
import { Modal, View, Text, TextInput, TouchableOpacity, Alert, Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { createCuentaEmbedded } from '../firebase/firestoreService';
import { db } from 'utils/firebase.js';
import { collection, addDoc, serverTimestamp } from 'firebase/firestore';

interface Props {
visible: boolean;
Expand All @@ -10,6 +11,7 @@ interface Props {
}

export default function AddCuenta({ visible, onClose, onSaved }: Props) {
const { user } = useAuth();
const [tipo, setTipo] = useState<'corriente' | 'ahorros'>('corriente');
const [numero, setNumero] = useState('');
const [saldo, setSaldo] = useState('0');
Expand All @@ -34,14 +36,23 @@ export default function AddCuenta({ visible, onClose, onSaved }: Props) {
}

const parsedSaldo = Number(saldo) || 0;
const payload = {
tipo,
numero,
saldo: parsedSaldo,
cedula,
propietario,
email: email || null,
createdAt: serverTimestamp(),
} as any;

setSaving(true);
try {
const cuenta = await createCuentaEmbedded({ nombre: propietario || 'Cuenta', balance: parsedSaldo, tipo, numero });
console.log('Cuenta embebida creada', cuenta);
const ref = await addDoc(collection(db, 'cuentas'), payload);
console.log('Cuenta creada', ref.id, payload);
reset();
onClose();
onSaved?.(cuenta.id);
onSaved?.(ref.id);
} catch (e) {
console.warn('Failed to save embedded account to user doc', e);
// Fallback: just log and close
Expand Down
26 changes: 26 additions & 0 deletions components/ChartsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,31 @@ export default function ChartsScreen({ onBack, onPressAdd, onPressHome, onPressE
setCuentas([]);
}

<<<<<<< HEAD
// Cargar transacciones
const snap = await getDocs(collection(db, 'transacciones'));
const docs = snap.docs.map(d => {
const data = d.data() as any;
return {
id: d.id,
tipo: data.type ?? data.tipo ?? 'expense',
categoria: data.category ?? data.categoria ?? '',
monto: Number(data.amount ?? data.monto ?? 0),
fecha: data.date ?? data.fecha ?? (data.createdAt ? data.createdAt.toDate().toString() : ''),
account: data.account ?? data.cuenta ?? '',
accountId: data.accountId ?? '',
} as TransaccionConCuenta;
});

// Ordenar por fecha descendente
docs.sort((a, b) => {
const ta = a.fecha ? new Date(a.fecha).getTime() : 0;
const tb = b.fecha ? new Date(b.fecha).getTime() : 0;
return tb - ta;
});

setTodasLasTransacciones(docs);
=======
// Cargar transacciones desde la colección top-level `transacciones`
try {
const auth = getAuth();
Expand All @@ -104,6 +129,7 @@ export default function ChartsScreen({ onBack, onPressAdd, onPressHome, onPressE
console.warn('Error cargando transacciones', err);
setTodasLasTransacciones([]);
}
>>>>>>> origin/main
} catch (error) {
console.warn('Error cargando datos:', error);
setTodasLasTransacciones([]);
Expand Down
52 changes: 42 additions & 10 deletions components/DetalleCuenta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,31 @@ export default function DetalleCuenta({ cuenta, onBack }: DetalleCuentaProps) {
useEffect(() => {
const loadForAccount = async () => {
try {
// Load recent transactions for the current user and filter locally
const auth = getAuth();
const user = auth.currentUser;
if (!user) {
setTransactions([]);
return;
}

const txs = await getRecentTransactionsForUser(user.uid, 200);
const docs = txs.map((d: any) => ({
// If cuenta.numero is available, query by accountId field in Firestore
if (cuenta && (cuenta as any).numero) {
const accNum = (cuenta as any).numero;
// query registro where accountId == accNum ordered by date desc
const q = query(collection(db, 'registro'), where('accountId', '==', accNum), orderBy('date', 'desc'));
const snap = await getDocs(q);
const docs = snap.docs.map(d => {
const data = d.data() as any;
return {
id: d.id,
tipo: data.type ?? data.tipo ?? 'expense',
categoria: data.category ?? data.categoria ?? '',
monto: Number(data.amount ?? data.monto ?? 0),
fecha: data.date ?? data.fecha ?? (data.createdAt ? data.createdAt.toDate().toString() : ''),
} as Transaccion;
});
setTransactions(docs);
return;
}

// Fallback: load all and filter by name/id (best-effort)
const snap = await getDocs(collection(db, 'registro'));
const docs = snap.docs.map(d => {
const data = d.data() as any;
return {
id: d.id,
tipo: d.tipo ?? d.type ?? d.tipo ?? 'expense',
categoria: d.categoria ?? d.category ?? '',
Expand Down Expand Up @@ -89,6 +104,23 @@ export default function DetalleCuenta({ cuenta, onBack }: DetalleCuentaProps) {
</TouchableOpacity>
</View>

{/* Detalles de la cuenta */}
<View className="mx-4 mt-4 bg-neutral-900 rounded-2xl p-4 border border-neutral-800">
<Text className="text-neutral-400 text-xs font-semibold mb-3">DETALLES DE LA CUENTA</Text>
<View className="flex-row justify-between mb-2">
<Text className="text-neutral-400 text-sm">Número</Text>
<Text className="text-white text-sm">{(cuenta as any).numero || '—'}</Text>
</View>
<View className="flex-row justify-between mb-2">
<Text className="text-neutral-400 text-sm">Tipo</Text>
<Text className="text-white text-sm">{(cuenta as any).tipo ? ((cuenta as any).tipo as string).toUpperCase() : '—'}</Text>
</View>
<View className="flex-row justify-between">
<Text className="text-neutral-400 text-sm">Balance</Text>
<Text className="text-white text-sm font-semibold">${cuenta.balance.toFixed(2)}</Text>
</View>
</View>

<ScrollView
className="flex-1"
showsVerticalScrollIndicator={false}
Expand Down
49 changes: 23 additions & 26 deletions components/Estadisticas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,32 +79,29 @@ export default function Estadisticas({ onBack, onPressAdd, onPressHome, onPressC
setCuentas([]);
}

// Cargar transacciones top-level
try {
const auth = getAuth();
const user = auth.currentUser;
if (user) {
const snapTx = await getRecentTransactionsForUser(user.uid, 200);
const docs = snapTx.map((d: any) => ({
id: d.id,
tipo: d.tipo ?? d.type ?? 'expense',
categoria: d.categoria ?? d.category ?? '',
monto: Number(d.monto ?? d.amount ?? 0),
fecha: d.fecha ?? d.date ?? (d.createdAt ? (d.createdAt.toDate ? d.createdAt.toDate().toString() : d.createdAt) : ''),
account: d.account ?? d.cuenta ?? '',
accountId: d.cuentaId ?? d.accountId ?? '',
} as TransaccionConCuenta));
docs.sort((a: TransaccionConCuenta, b: TransaccionConCuenta) => {
const ta = a.fecha ? new Date(a.fecha).getTime() : 0;
const tb = b.fecha ? new Date(b.fecha).getTime() : 0;
return tb - ta;
});
setTodasLasTransacciones(docs);
}
} catch (err) {
console.warn('Error cargando transacciones', err);
setTodasLasTransacciones([]);
}
// Cargar transacciones (patrón de Home.tsx)
const snap = await getDocs(collection(db, 'registro'));
const docs = snap.docs.map(d => {
const data = d.data() as any;
return {
id: d.id,
tipo: data.type ?? data.tipo ?? 'expense',
categoria: data.category ?? data.categoria ?? '',
monto: Number(data.amount ?? data.monto ?? 0),
fecha: data.date ?? data.fecha ?? (data.createdAt ? data.createdAt.toDate().toString() : ''),
account: data.account ?? data.cuenta ?? '',
accountId: data.accountId ?? '',
} as TransaccionConCuenta;
});

// Ordenar por fecha descendente
docs.sort((a, b) => {
const ta = a.fecha ? new Date(a.fecha).getTime() : 0;
const tb = b.fecha ? new Date(b.fecha).getTime() : 0;
return tb - ta;
});

setTodasLasTransacciones(docs);
} catch (error) {
console.warn('Error cargando datos:', error);
setTodasLasTransacciones([]);
Expand Down
Loading