Architecture Overview
Sistema Financiero follows a full-stack Next.js architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────┐
│ Next.js 15 (Full-Stack Framework) │
├─────────────────────────────────────────────────────┤
│ FRONTEND (Client-Side) │
│ ├─ app/page.tsx → Dashboard │
│ ├─ app/registro/ → Manual forms │
│ ├─ app/agente-mejorado/ → AI chat │
│ └─ components/*.tsx → Reusable UI │
├─────────────────────────────────────────────────────┤
│ BACKEND (Server-Side) │
│ └─ app/api/ → API Routes │
│ ├─ transacciones/route.ts → GET transactions │
│ ├─ chat/stream/route.ts → AI streaming │
│ └─ upload-image/route.ts → OCR processing │
├─────────────────────────────────────────────────────┤
│ DATABASE (Supabase) │
│ └─ transacciones table │
└─────────────────────────────────────────────────────┘
Common Patterns
Pattern 1: Adding a New API Endpoint
Create Route File
Create a new route handler in app/api/: // File: app/api/stats/route.ts
import { NextRequest , NextResponse } from 'next/server'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY !
)
export async function GET ( request : NextRequest ) {
const { data , error } = await supabase
. from ( 'transacciones' )
. select ( 'tipo, monto' )
if ( error ) {
return NextResponse . json ({ error: error . message }, { status: 500 })
}
const stats = data . reduce (
( acc , t ) => {
if ( t . tipo === 'ingreso' ) {
acc . totalIngresos += Number ( t . monto )
} else {
acc . totalGastos += Number ( t . monto )
}
return acc
},
{ totalIngresos: 0 , totalGastos: 0 }
)
return NextResponse . json ({ data: stats })
}
Call from Frontend
Use the endpoint in your component: const [ stats , setStats ] = useState ({ totalIngresos: 0 , totalGastos: 0 })
useEffect (() => {
fetch ( '/api/stats' )
. then ( res => res . json ())
. then ( json => setStats ( json . data ))
}, [])
API routes in app/api/ are automatically server-side and have access to environment variables.
Pattern 2: Adding a New Category
Categories are hardcoded in multiple files. Update all locations to avoid inconsistencies.
Update AI Chat Categories
File: app/api/chat/stream/route.ts:26 📋 CATEGORÍAS VÁLIDAS :
** Gastos : ** Alimentación , Transporte , Vivienda , Salud , Entretenimiento , Educación , Mascotas , Otros Gastos
** Ingresos : ** Salario , Ventas , Servicios , Inversiones , Otros Ingresos
Update Function Definitions
File: app/api/chat/stream/route.ts:75 categoria : {
type : 'string' ,
enum : [
'Alimentación' ,
'Transporte' ,
'Vivienda' ,
'Salud' ,
'Entretenimiento' ,
'Educación' ,
'Mascotas' , // ← New category
'Otros Gastos'
]
}
Update Manual Form
File: app/registro/page.tsx (add to category dropdown) const CATEGORIAS_GASTOS = [
'Alimentación' ,
'Transporte' ,
'Vivienda' ,
'Salud' ,
'Entretenimiento' ,
'Educación' ,
'Mascotas' , // ← New category
'Otros Gastos'
]
Update OCR Analysis
File: app/api/upload-image/route.ts:64 ** CATEGORÍAS VÁLIDAS DEL SISTEMA : **
- Gastos : Alimentación , Transporte , Vivienda , Salud , Entretenimiento , Educación , Mascotas , Otros Gastos
Pattern 3: Adding a Database Column
Add Column to Supabase
Run in Supabase SQL Editor: ALTER TABLE transacciones ADD COLUMN notas TEXT ;
Update TypeScript Interface
File: components/DataViews.tsx:8 interface Transaccion {
id : string
fecha : string
tipo : 'gasto' | 'ingreso'
categoria : string
monto : number
descripcion : string
metodo_pago : string
notas : string | null // ← New field
}
Update API Insert Logic
File: app/api/chat/stream/route.ts:208 const { error } = await supabase . from ( 'transacciones' ). insert ({
tipo ,
monto: functionArgs . monto ,
categoria: functionArgs . categoria ,
concepto: functionArgs . descripcion || ` ${ tipo } - ${ functionArgs . categoria } ` ,
descripcion: functionArgs . descripcion || null ,
metodo_pago: functionArgs . metodo_pago || 'Efectivo' ,
registrado_por: functionArgs . registrado_por || 'Usuario' ,
notas: functionArgs . notas || null , // ← New field
fecha: new Date (). toISOString (),
})
Update AI Function Schema
Add to function parameters: parameters : {
type : 'object' ,
properties : {
monto : { type : 'number' },
categoria : { type : 'string' , enum : [ ... ] },
descripcion : { type : 'string' },
metodo_pago : { type : 'string' , enum : [ ... ] },
registrado_por : { type : 'string' },
notas : { type : 'string' , description : 'Notas adicionales' } // ← New
},
required : [ 'monto' , 'categoria' ]
}
Pattern 4: Adding a New Component
Create Component File
// File: components/ExpenseChart.tsx
'use client'
import { useState , useEffect } from 'react'
import { Pie } from 'react-chartjs-2'
export function ExpenseChart () {
const [ chartData , setChartData ] = useState ( null )
useEffect (() => {
fetch ( '/api/transacciones?vista=mensual' )
. then ( res => res . json ())
. then ( json => {
const gastos = json . data . filter ( t => t . tipo === 'gasto' )
const byCategory = gastos . reduce (( acc , t ) => {
acc [ t . categoria ] = ( acc [ t . categoria ] || 0 ) + Number ( t . monto )
return acc
}, {})
setChartData ({
labels: Object . keys ( byCategory ),
datasets: [{
data: Object . values ( byCategory ),
backgroundColor: [ '#FF6384' , '#36A2EB' , '#FFCE56' , '#4BC0C0' ]
}]
})
})
}, [])
if ( ! chartData ) return < div > Loading ...</ div >
return < Pie data = { chartData } />
}
Import and Use
// File: app/page.tsx
import { ExpenseChart } from '@/components/ExpenseChart'
export default function Dashboard () {
return (
< div >
< h1 > Dashboard </ h1 >
< ExpenseChart />
</ div >
)
}
Feature Examples
Example 1: Add Budget Tracking
SQL Migration
API Endpoint
Component
-- Add budget column to transacciones
ALTER TABLE transacciones ADD COLUMN presupuesto NUMERIC ( 10 , 2 );
-- Create budget alerts table
CREATE TABLE presupuestos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
usuario_id UUID REFERENCES auth . users (id),
categoria TEXT NOT NULL ,
monto_limite NUMERIC ( 10 , 2 ) NOT NULL ,
periodo TEXT CHECK (periodo IN ( 'mensual' , 'semanal' , 'diario' )),
created_at TIMESTAMP DEFAULT NOW ()
);
Example 2: Add Recurring Transactions
CREATE TABLE transacciones_recurrentes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
usuario_id UUID REFERENCES auth . users (id),
tipo TEXT CHECK (tipo IN ( 'ingreso' , 'gasto' )) NOT NULL ,
monto NUMERIC ( 10 , 2 ) NOT NULL ,
categoria TEXT NOT NULL ,
descripcion TEXT ,
frecuencia TEXT CHECK (frecuencia IN ( 'diaria' , 'semanal' , 'mensual' , 'anual' )),
dia_mes INTEGER CHECK (dia_mes BETWEEN 1 AND 31 ),
activo BOOLEAN DEFAULT true,
proxima_fecha DATE NOT NULL ,
created_at TIMESTAMP DEFAULT NOW ()
);
CREATE INDEX idx_recurrentes_proxima_fecha ON transacciones_recurrentes(proxima_fecha);
Changing AI Model
File: app/api/chat/stream/route.ts:54
const response = await fetch ( 'https://openrouter.ai/api/v1/chat/completions' , {
body: JSON . stringify ({
model: 'anthropic/claude-3.5-sonnet' , // ← Change model here
// Available models:
// - 'google/gemini-2.5-flash' (default, fastest)
// - 'anthropic/claude-3.5-sonnet' (best quality)
// - 'openai/gpt-4o' (balanced)
// - 'meta-llama/llama-3.1-70b-instruct' (open source)
})
})
Custom Hooks
Example: useTransactions Hook
// File: hooks/useTransactions.ts
import { useState , useEffect } from 'react'
import { supabase } from '@/lib/supabase'
interface Transaccion {
id : string
fecha : string
tipo : 'gasto' | 'ingreso'
categoria : string
monto : number
}
export function useTransactions ( vista : 'diaria' | 'semanal' | 'mensual' = 'mensual' ) {
const [ transacciones , setTransacciones ] = useState < Transaccion []>([])
const [ loading , setLoading ] = useState ( true )
const [ error , setError ] = useState < string | null >( null )
useEffect (() => {
async function fetchData () {
try {
const { data , error } = await supabase
. from ( 'transacciones' )
. select ( '*' )
. order ( 'fecha' , { ascending: false })
if ( error ) throw error
setTransacciones ( data || [])
} catch ( err : any ) {
setError ( err . message )
} finally {
setLoading ( false )
}
}
fetchData ()
}, [ vista ])
return { transacciones , loading , error }
}
Testing New Features
Check Console Logs
Open DevTools → Console to see errors or API responses
Test Database Changes
Use Supabase Dashboard → Table Editor to verify inserts
Test API Endpoints
Use curl or Postman: curl http://localhost:3000/api/stats
Next Steps
Database Schema Learn about the database structure
Customization Guide Customize categories, styles, and behavior