Álvaro Valenzuela Valdes commited on
Commit
42df68f
·
1 Parent(s): 9b881be

feat: Full UI/UX overhaul with Seed Data and Agentic Sync

Browse files
backend/requirements.txt CHANGED
@@ -6,3 +6,6 @@ pydantic-settings==2.4.0
6
  google-generativeai==0.7.2
7
  pypdf==4.2.0
8
  python-multipart==0.0.9
 
 
 
 
6
  google-generativeai==0.7.2
7
  pypdf==4.2.0
8
  python-multipart==0.0.9
9
+ sqlalchemy==2.0.25
10
+ pymysql==1.1.0
11
+ cryptography==42.0.2
backend/seed_db.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ from sqlalchemy.orm import Session
4
+ from datetime import datetime, timedelta
5
+
6
+ # Add parent dir to path to import app
7
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
+
9
+ from app.database import SessionLocal, engine, Base
10
+ from app.models.tender import TenderModel
11
+ from app.models.analysis import AnalysisHistoryModel
12
+ from app.models.company import CompanyProfileModel
13
+
14
+ def seed():
15
+ Base.metadata.drop_all(bind=engine)
16
+ Base.metadata.create_all(bind=engine)
17
+
18
+ db = SessionLocal()
19
+
20
+ # 1. Company Profile
21
+ profile = CompanyProfileModel(
22
+ name="Andes Digital Solutions",
23
+ industry="Software & AI Engineering",
24
+ services="Machine Learning, Web Development, Cloud Infrastructure",
25
+ experience="Over 10 years delivering high-impact digital products in Latin America.",
26
+ certifications="AWS Certified, ISO 27001",
27
+ regions="Metropolitana, Valparaíso, Biobío",
28
+ documents_available="RUT, Financial Statements 2023, Portfolio, Technical Team CVs"
29
+ )
30
+ db.add(profile)
31
+
32
+ # 2. Sample Tenders
33
+ tenders = [
34
+ TenderModel(
35
+ code="1234-56-LE24",
36
+ name="Plataforma de Gestión Documental para el MINVU",
37
+ description="Desarrollo e implementación de una plataforma basada en la nube para la gestión de subsidios habitacionales.",
38
+ buyer="Ministerio de Vivienda y Urbanismo",
39
+ status="Publicada",
40
+ closing_date=(datetime.now() + timedelta(days=15)).strftime("%Y-%m-%d"),
41
+ estimated_amount=120000000,
42
+ region="Metropolitana",
43
+ sector="Tecnología de la Información",
44
+ source="Mercado Público"
45
+ ),
46
+ TenderModel(
47
+ code="7890-12-LP24",
48
+ name="Servicio de Consultoría IA para Modernización del Estado",
49
+ description="Estudio y prototipado de agentes de IA para atención ciudadana automatizada.",
50
+ buyer="Secretaría General de la Presidencia",
51
+ status="Publicada",
52
+ closing_date=(datetime.now() + timedelta(days=5)).strftime("%Y-%m-%d"),
53
+ estimated_amount=45000000,
54
+ region="Metropolitana",
55
+ sector="Consultoría",
56
+ source="Mercado Público"
57
+ ),
58
+ TenderModel(
59
+ code="5544-33-LE24",
60
+ name="Soporte y Mantención Infraestructura Crítica",
61
+ description="Mantenimiento preventivo y correctivo de servidores y redes institucionales.",
62
+ buyer="Gobierno Regional de Valparaíso",
63
+ status="Publicada",
64
+ closing_date=(datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d"),
65
+ estimated_amount=75000000,
66
+ region="Valparaíso",
67
+ sector="Servicios Técnicos",
68
+ source="Mercado Público"
69
+ )
70
+ ]
71
+ for t in tenders:
72
+ db.add(t)
73
+
74
+ # 3. Sample Analysis History
75
+ history = AnalysisHistoryModel(
76
+ tender_code="1234-56-LE24",
77
+ tender_name="Plataforma de Gestión Documental para el MINVU",
78
+ decision="Recommended",
79
+ score=85,
80
+ summary="La licitación se alinea perfectamente con las capacidades de Andes Digital en desarrollo cloud y gestión de datos.",
81
+ risks='[{"severity": "Low", "description": "Plazos de entrega ajustados"}, {"severity": "Medium", "description": "Requisito de boleta de garantía alta"}]',
82
+ technical_analysis="Factibilidad técnica alta. Requiere stack de Python/React.",
83
+ legal_analysis="Cumplimos con todas las bases administrativas.",
84
+ commercial_analysis="Margen proyectado del 25% tras costos operacionales.",
85
+ proposal_draft="Se propone una arquitectura basada en microservicios...",
86
+ report_markdown="# Reporte Ejecutivo: MINVU Platform\n\n## Conclusión\nLicitación ganable...",
87
+ created_at=datetime.now()
88
+ )
89
+ db.add(history)
90
+
91
+ db.commit()
92
+ print("Database seeded successfully!")
93
+ db.close()
94
+
95
+ if __name__ == "__main__":
96
+ seed()
backend_tunnel.txt ADDED
Binary file (1.9 kB). View file
 
backend_tunnel_v3.txt ADDED
Binary file (1.9 kB). View file
 
frontend/.env.local ADDED
@@ -0,0 +1 @@
 
 
1
+ NEXT_PUBLIC_API_BASE=https://tidy-glasses-cover.loca.lt
frontend/app/page.tsx CHANGED
@@ -9,6 +9,7 @@ import ProposalDraft from "../components/ProposalDraft";
9
  import Reports from "../components/Reports";
10
  import Sidebar from "../components/Sidebar";
11
  import AnalysisHistory from "../components/AnalysisHistory";
 
12
  import { analyzeTender, fetchAnalysisHistory, fetchCompanyProfile, healthCheck, saveCompanyProfile, searchTenders } from "../lib/api";
13
  import type { AnalysisHistoryItem, AnalysisResult, CompanyProfile as CompanyProfileType, Tender } from "../lib/types";
14
 
@@ -27,6 +28,7 @@ type Tab = (typeof tabs)[number];
27
 
28
  export default function HomePage() {
29
  const [activeTab, setActiveTab] = useState<Tab>("Dashboard");
 
30
  const [tenders, setTenders] = useState<Tender[]>([]);
31
  const [selectedTender, setSelectedTender] = useState<Tender | null>(null);
32
  const [companyProfile, setCompanyProfile] = useState<CompanyProfileType>({
@@ -120,6 +122,7 @@ export default function HomePage() {
120
 
121
  return (
122
  <div className="min-h-screen">
 
123
  <div className="mx-auto flex min-h-screen max-w-[1500px] gap-8 px-8 py-6">
124
  <Sidebar
125
  tabs={tabs}
 
9
  import Reports from "../components/Reports";
10
  import Sidebar from "../components/Sidebar";
11
  import AnalysisHistory from "../components/AnalysisHistory";
12
+ import GlobalSync from "../components/GlobalSync";
13
  import { analyzeTender, fetchAnalysisHistory, fetchCompanyProfile, healthCheck, saveCompanyProfile, searchTenders } from "../lib/api";
14
  import type { AnalysisHistoryItem, AnalysisResult, CompanyProfile as CompanyProfileType, Tender } from "../lib/types";
15
 
 
28
 
29
  export default function HomePage() {
30
  const [activeTab, setActiveTab] = useState<Tab>("Dashboard");
31
+ const [showSync, setShowSync] = useState(true);
32
  const [tenders, setTenders] = useState<Tender[]>([]);
33
  const [selectedTender, setSelectedTender] = useState<Tender | null>(null);
34
  const [companyProfile, setCompanyProfile] = useState<CompanyProfileType>({
 
122
 
123
  return (
124
  <div className="min-h-screen">
125
+ {showSync && <GlobalSync onComplete={() => setShowSync(false)} />}
126
  <div className="mx-auto flex min-h-screen max-w-[1500px] gap-8 px-8 py-6">
127
  <Sidebar
128
  tabs={tabs}
frontend/components/AgentAnalysis.tsx CHANGED
@@ -12,9 +12,9 @@ type Props = {
12
  };
13
 
14
  const agents = [
15
- { id: "legal", name: "Dra. Legal", role: "Compliance", avatar: "⚖️", color: "text-amber-400" },
16
- { id: "tech", name: "Ing. Tech", role: "Architecture", avatar: "👨‍💻", color: "text-cyan" },
17
- { id: "risk", name: "Sra. Estrategia", role: "ROI & Risk", avatar: "🕵️‍♀️", color: "text-purple-400" },
18
  ];
19
 
20
  export default function AgentAnalysis({ tender, companyProfile, analysis, onAnalyze }: Props) {
@@ -50,68 +50,103 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
50
  setIsRunning(false);
51
  };
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  return (
54
- <div className="space-y-8">
55
  {/* Tender Header Card */}
56
- <div className="relative overflow-hidden rounded-3xl border border-slate-800 bg-slate-900/40 p-8 shadow-2xl">
57
- <div className="absolute -right-20 -top-20 h-64 w-64 rounded-full bg-cyan/5 blur-[100px]" />
58
  <div className="relative z-10 flex flex-col gap-8 lg:flex-row lg:items-center lg:justify-between">
59
  <div className="max-w-3xl">
60
  <div className="flex items-center gap-3 mb-4">
61
- <span className="rounded-full bg-cyan/10 px-3 py-1 text-[10px] font-bold uppercase tracking-widest text-cyan">Licitación Seleccionada</span>
62
  <span className="text-xs text-slate-500 font-mono">{tender?.code}</span>
63
  </div>
64
- <h2 className="text-4xl font-bold text-white tracking-tight leading-tight">{tender?.name ?? "Esperando selección..."}</h2>
65
- <p className="mt-4 text-slate-400 font-medium">{tender?.buyer ?? "Busca una oportunidad en Tender Search para iniciar la mesa de expertos."}</p>
66
 
67
  {tender && (
68
  <div className="mt-8 flex flex-wrap gap-4">
69
- <div className="rounded-2xl bg-slate-950/50 p-4 border border-slate-800/50">
70
- <p className="text-[10px] uppercase text-slate-500 font-bold mb-1">Monto Estimado</p>
71
  <p className="text-lg font-semibold text-white">
72
- {tender.estimated_amount ? new Intl.NumberFormat("es-CL", { style: "currency", currency: "CLP", maximumFractionDigits: 0 }).format(tender.estimated_amount) : "No declarado"}
73
  </p>
74
  </div>
75
- <div className="rounded-2xl bg-slate-950/50 p-4 border border-slate-800/50">
76
- <p className="text-[10px] uppercase text-slate-500 font-bold mb-1">Cierre Técnico</p>
77
  <p className="text-lg font-semibold text-white">{tender.closing_date}</p>
78
  </div>
79
  </div>
80
  )}
81
  </div>
82
 
83
- <div className="flex flex-col gap-4 lg:w-72">
84
- <div className="rounded-2xl border border-slate-800 bg-slate-950 p-6">
85
- <h4 className="text-xs font-bold uppercase text-slate-400 mb-4 tracking-tighter">Bases Técnicas (PDF)</h4>
86
- <input type="file" accept=".pdf" onChange={handleFileChange} className="w-full text-xs text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-[10px] file:font-bold file:bg-slate-800 file:text-cyan hover:file:bg-slate-700 transition cursor-pointer" />
87
- {file && <p className="mt-2 text-[10px] text-green-400">✓ Archivo cargado correctamente</p>}
88
  </div>
89
 
90
- <label className="flex items-center gap-3 p-3 rounded-2xl bg-slate-950/50 cursor-pointer hover:bg-slate-950 transition">
91
- <input type="checkbox" checked={approved} onChange={(e) => setApproved(e.target.checked)} className="h-5 w-5 rounded border-slate-700 bg-slate-950 text-cyan outline-none" />
92
- <span className="text-[11px] font-semibold text-slate-300">Autorizar mesa de expertos</span>
93
  </label>
94
 
95
  <button
96
  onClick={handleAnalyzeClick}
97
  disabled={!tender || !approved || isRunning || isUploading}
98
- className="w-full rounded-2xl bg-cyan py-5 font-bold text-slate-950 transition hover:bg-sky disabled:opacity-30 disabled:cursor-not-allowed shadow-[0_0_20px_rgba(6,182,212,0.15)]"
99
  >
100
- {isUploading ? "Cargando Bases..." : isRunning ? "Agentes Debatiendo..." : "Iniciar Sesión Agéntica"}
101
  </button>
102
  </div>
103
  </div>
104
  </div>
105
 
106
- {/* Agents Row (War Room) */}
107
- <div className="grid gap-4 md:grid-cols-3">
108
  {agents.map((agent) => (
109
- <div key={agent.id} className={`rounded-3xl border border-slate-800 bg-slate-900/20 p-6 flex items-center gap-4 transition duration-500 ${isRunning ? 'animate-pulse' : ''} ${analysis ? 'border-slate-700 bg-slate-900/40' : ''}`}>
110
- <div className="text-3xl grayscale-[0.5] hover:grayscale-0 transition">{agent.avatar}</div>
111
  <div>
112
- <div className={`text-xs font-bold uppercase tracking-widest ${agent.color}`}>{agent.role}</div>
113
- <div className="text-sm font-semibold text-white">{agent.name}</div>
114
- {analysis && <div className="text-[10px] text-green-400 mt-1">Conclusión generada ✓</div>}
115
  </div>
116
  </div>
117
  ))}
@@ -120,54 +155,57 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
120
  {/* Analysis Results View */}
121
  {analysis && (
122
  <div className="grid gap-8 lg:grid-cols-12 animate-in fade-in slide-in-from-bottom-8 duration-1000">
123
- {/* Main Fit Score & Summary */}
124
  <div className="lg:col-span-8 space-y-8">
125
- <div className="rounded-3xl border border-slate-800 bg-slate-950/80 p-8 shadow-inner">
126
- <div className="flex items-start justify-between">
127
  <div>
128
- <div className="text-[11px] font-bold uppercase tracking-[0.3em] text-cyan mb-2">Consenso de la Mesa</div>
129
- <h3 className="text-5xl font-black text-white">{analysis.fit_score}% <span className="text-2xl font-normal text-slate-500">Fit Score</span></h3>
130
  </div>
131
- <div className={`rounded-2xl px-4 py-2 text-xs font-bold uppercase ${analysis.decision === 'Recommended' ? 'bg-green-500/20 text-green-400' : 'bg-amber-500/20 text-amber-400'}`}>
132
  {analysis.decision}
133
  </div>
134
  </div>
135
- <div className="mt-8 prose prose-invert max-w-none">
136
- <p className="text-slate-300 text-lg leading-relaxed italic border-l-4 border-cyan pl-6">{analysis.executive_summary}</p>
137
  </div>
138
  </div>
139
 
140
  <div className="grid gap-6 md:grid-cols-2">
141
- <div className="rounded-3xl border border-slate-800 bg-slate-900/40 p-6">
142
- <h4 className="text-[11px] font-bold uppercase tracking-widest text-amber-400 mb-6">Gaps de Cumplimiento (Legal)</h4>
 
 
143
  <ul className="space-y-4">
144
  {analysis.compliance_gaps.map((gap, i) => (
145
- <li key={i} className="flex gap-3 text-sm text-slate-300">
146
- <span className="text-amber-400"></span> {gap}
147
  </li>
148
  ))}
149
  </ul>
150
  </div>
151
- <div className="rounded-3xl border border-slate-800 bg-slate-900/40 p-6">
152
- <h4 className="text-[11px] font-bold uppercase tracking-widest text-cyan mb-6">Requerimientos Clave (Técnico)</h4>
 
 
153
  <ul className="space-y-4">
154
  {analysis.key_requirements.map((req, i) => (
155
- <li key={i} className="flex gap-3 text-sm text-slate-300">
156
- <span className="text-cyan">▹</span> {req}
157
  </li>
158
  ))}
159
  </ul>
160
  </div>
161
  </div>
162
 
163
- <div className="rounded-3xl border border-slate-800 bg-slate-900/40 p-8">
164
- <h4 className="text-[11px] font-bold uppercase tracking-widest text-purple-400 mb-8 text-center">Matriz de Riesgos Identificados</h4>
165
- <div className="grid gap-4 md:grid-cols-2">
166
  {analysis.risks.map((risk, i) => (
167
- <div key={i} className="group rounded-2xl bg-slate-950 p-5 border border-slate-800 hover:border-purple-500/50 transition">
168
- <div className="flex items-center justify-between mb-3">
169
- <span className="font-bold text-white group-hover:text-purple-400 transition">{risk.title}</span>
170
- <span className={`text-[9px] font-black px-2 py-0.5 rounded uppercase ${risk.severity === 'High' ? 'bg-red-500/20 text-red-500' : 'bg-slate-800 text-slate-400'}`}>{risk.severity}</span>
171
  </div>
172
  <p className="text-xs text-slate-500 leading-relaxed">{risk.explanation}</p>
173
  </div>
@@ -176,22 +214,21 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
176
  </div>
177
  </div>
178
 
179
- {/* Right Col: Audit Trail & Conversation */}
180
- <div className="lg:col-span-4 space-y-6">
181
- <div className="rounded-3xl border border-slate-800 bg-slate-950 p-6 flex flex-col h-full shadow-2xl">
182
- <div className="flex items-center gap-3 mb-8 border-b border-slate-800 pb-4">
183
- <div className="h-2 w-2 rounded-full bg-cyan animate-pulse shadow-[0_0_10px_rgba(6,182,212,0.5)]" />
184
- <h4 className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Audit Trail & Agents Log</h4>
185
  </div>
186
- <div className="flex-1 space-y-6 overflow-y-auto max-h-[600px] pr-2 custom-scrollbar">
187
  {analysis.audit_log?.map((log, i) => (
188
- <div key={i} className="flex gap-4 group">
189
  <div className="flex flex-col items-center">
190
- <div className="h-6 w-6 rounded-full bg-slate-900 flex items-center justify-center text-[10px] border border-slate-800 group-hover:border-cyan transition">🤖</div>
191
- {i < (analysis.audit_log?.length ?? 0) - 1 && <div className="w-[1px] flex-1 bg-slate-800 my-2" />}
192
  </div>
193
- <div className="pb-4">
194
- <p className="text-xs text-slate-400 leading-relaxed group-hover:text-slate-200 transition">{log}</p>
195
  </div>
196
  </div>
197
  ))}
@@ -200,14 +237,6 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
200
  </div>
201
  </div>
202
  )}
203
-
204
- {!analysis && !isRunning && (
205
- <div className="rounded-3xl border border-dashed border-slate-800 bg-slate-950/20 p-20 flex flex-col items-center justify-center text-center">
206
- <div className="text-6xl mb-6 grayscale opacity-20">🤖⚖️👨‍💻🕵️‍♀️</div>
207
- <h3 className="text-xl font-bold text-slate-500">Mesa de expertos inactiva</h3>
208
- <p className="mt-2 text-sm text-slate-600 max-w-sm">Selecciona una licitación y carga las bases para iniciar el debate agéntico entre nuestros especialistas.</p>
209
- </div>
210
- )}
211
  </div>
212
  );
213
  }
 
12
  };
13
 
14
  const agents = [
15
+ { id: "legal", name: "Dra. Legal", role: "Compliance", avatar: "⚖️", color: "text-amber-400", desc: "Verifies administrative bases and legal risks." },
16
+ { id: "tech", name: "Ing. Tech", role: "Architecture", avatar: "👨‍💻", color: "text-cyan", desc: "Evaluates technical feasibility and stack requirements." },
17
+ { id: "risk", name: "Sra. Estrategia", role: "ROI & Risk", avatar: "🕵️‍♀️", color: "text-purple-400", desc: "Calculates commercial impact and win probability." },
18
  ];
19
 
20
  export default function AgentAnalysis({ tender, companyProfile, analysis, onAnalyze }: Props) {
 
50
  setIsRunning(false);
51
  };
52
 
53
+ if (!tender && !analysis) {
54
+ return (
55
+ <div className="flex flex-col items-center justify-center min-h-[60vh] space-y-12 animate-in fade-in duration-1000">
56
+ <div className="text-center space-y-4">
57
+ <div className="inline-block p-4 rounded-3xl bg-white/5 border border-white/10 mb-6">
58
+ <span className="text-5xl">🤖</span>
59
+ </div>
60
+ <h2 className="text-4xl font-bold text-white tracking-tight">Agent War Room</h2>
61
+ <p className="text-slate-400 max-w-md mx-auto text-lg leading-relaxed">
62
+ Our specialized agents are ready to analyze your next big opportunity.
63
+ Select a tender from <span className="text-purple-400 font-bold italic">Tender Search</span> to begin.
64
+ </p>
65
+ </div>
66
+
67
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl w-full px-4">
68
+ {agents.map(agent => (
69
+ <div key={agent.id} className="glass-card rounded-3xl p-8 space-y-4 border border-white/5">
70
+ <div className="text-4xl">{agent.avatar}</div>
71
+ <div>
72
+ <h4 className={`text-xs font-bold uppercase tracking-widest ${agent.color} mb-1`}>{agent.role}</h4>
73
+ <h3 className="text-xl font-bold text-white mb-2">{agent.name}</h3>
74
+ <p className="text-sm text-slate-500 leading-relaxed">{agent.desc}</p>
75
+ </div>
76
+ </div>
77
+ ))}
78
+ </div>
79
+
80
+ <div className="flex items-center gap-3 text-slate-600 animate-bounce">
81
+ <span className="text-sm font-bold uppercase tracking-widest">Go to Tender Search</span>
82
+ <span className="text-xl">↓</span>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+
88
  return (
89
+ <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
90
  {/* Tender Header Card */}
91
+ <div className="glass-card relative overflow-hidden rounded-3xl p-8 border border-white/10">
92
+ <div className="absolute -right-20 -top-20 h-64 w-64 rounded-full bg-purple-500/10 blur-[100px]" />
93
  <div className="relative z-10 flex flex-col gap-8 lg:flex-row lg:items-center lg:justify-between">
94
  <div className="max-w-3xl">
95
  <div className="flex items-center gap-3 mb-4">
96
+ <span className="rounded-full bg-purple-500/20 px-3 py-1 text-[10px] font-bold uppercase tracking-widest text-purple-300 border border-purple-500/30">Active Opportunity</span>
97
  <span className="text-xs text-slate-500 font-mono">{tender?.code}</span>
98
  </div>
99
+ <h2 className="text-4xl font-bold text-white tracking-tight leading-tight mb-4">{tender?.name}</h2>
100
+ <p className="text-slate-400 text-lg leading-relaxed">{tender?.buyer}</p>
101
 
102
  {tender && (
103
  <div className="mt-8 flex flex-wrap gap-4">
104
+ <div className="rounded-2xl bg-white/5 p-4 border border-white/5">
105
+ <p className="text-[10px] uppercase text-slate-500 font-bold mb-1">Estimated Amount</p>
106
  <p className="text-lg font-semibold text-white">
107
+ {tender.estimated_amount ? new Intl.NumberFormat("es-CL", { style: "currency", currency: "CLP", maximumFractionDigits: 0 }).format(tender.estimated_amount) : "Not disclosed"}
108
  </p>
109
  </div>
110
+ <div className="rounded-2xl bg-white/5 p-4 border border-white/5">
111
+ <p className="text-[10px] uppercase text-slate-500 font-bold mb-1">Closing Date</p>
112
  <p className="text-lg font-semibold text-white">{tender.closing_date}</p>
113
  </div>
114
  </div>
115
  )}
116
  </div>
117
 
118
+ <div className="flex flex-col gap-4 lg:w-80">
119
+ <div className="glass-card rounded-2xl p-6 bg-white/5 border border-white/10">
120
+ <h4 className="text-[10px] font-bold uppercase text-slate-400 mb-4 tracking-widest">Administrative Bases (PDF)</h4>
121
+ <input type="file" accept=".pdf" onChange={handleFileChange} className="w-full text-xs text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-[10px] file:font-bold file:bg-white/10 file:text-purple-300 hover:file:bg-white/20 transition cursor-pointer" />
122
+ {file && <p className="mt-2 text-[10px] text-green-400 font-bold">✓ Ready for extraction</p>}
123
  </div>
124
 
125
+ <label className="flex items-center gap-3 p-4 rounded-2xl bg-white/5 cursor-pointer hover:bg-white/10 transition border border-white/5">
126
+ <input type="checkbox" checked={approved} onChange={(e) => setApproved(e.target.checked)} className="h-5 w-5 rounded border-white/20 bg-black text-purple-500 outline-none accent-purple-500" />
127
+ <span className="text-xs font-semibold text-slate-300">Authorize Agent War Room</span>
128
  </label>
129
 
130
  <button
131
  onClick={handleAnalyzeClick}
132
  disabled={!tender || !approved || isRunning || isUploading}
133
+ className="w-full rounded-2xl premium-gradient py-5 font-bold text-white transition hover:opacity-90 disabled:opacity-30 disabled:cursor-not-allowed shadow-xl shadow-purple-500/20 active:scale-[0.98]"
134
  >
135
+ {isUploading ? "Extracting Text..." : isRunning ? "Agents Debating..." : "Launch Analysis Pipeline"}
136
  </button>
137
  </div>
138
  </div>
139
  </div>
140
 
141
+ {/* Agents Row (Visual feedback) */}
142
+ <div className="grid gap-6 md:grid-cols-3">
143
  {agents.map((agent) => (
144
+ <div key={agent.id} className={`glass-card rounded-3xl p-6 flex items-center gap-4 transition-all duration-700 ${isRunning ? 'ring-2 ring-purple-500/50 animate-pulse' : ''} ${analysis ? 'border-purple-500/30' : 'border-white/5'}`}>
145
+ <div className={`text-4xl ${isRunning ? 'animate-bounce' : ''}`}>{agent.avatar}</div>
146
  <div>
147
+ <div className={`text-[10px] font-bold uppercase tracking-widest ${agent.color}`}>{agent.role}</div>
148
+ <div className="text-sm font-bold text-white">{agent.name}</div>
149
+ {analysis && <div className="text-[9px] text-green-400 font-bold mt-1 uppercase tracking-tighter tracking-widest">Analysis Ready ✓</div>}
150
  </div>
151
  </div>
152
  ))}
 
155
  {/* Analysis Results View */}
156
  {analysis && (
157
  <div className="grid gap-8 lg:grid-cols-12 animate-in fade-in slide-in-from-bottom-8 duration-1000">
 
158
  <div className="lg:col-span-8 space-y-8">
159
+ <div className="glass-card rounded-3xl p-10 bg-white/[0.02]">
160
+ <div className="flex items-start justify-between mb-8">
161
  <div>
162
+ <div className="text-[11px] font-bold uppercase tracking-[0.3em] text-purple-400 mb-2">Agent Consensus</div>
163
+ <h3 className="text-6xl font-black text-white">{analysis.fit_score}% <span className="text-2xl font-light text-slate-500">Fit Score</span></h3>
164
  </div>
165
+ <div className={`rounded-2xl px-6 py-3 text-[10px] font-black uppercase tracking-widest shadow-lg ${analysis.decision === 'Recommended' ? 'bg-green-500/20 text-green-400 border border-green-500/30 shadow-green-500/10' : 'bg-amber-500/20 text-amber-400 border border-amber-500/30 shadow-amber-500/10'}`}>
166
  {analysis.decision}
167
  </div>
168
  </div>
169
+ <div className="prose prose-invert max-w-none">
170
+ <p className="text-slate-300 text-xl leading-relaxed italic border-l-4 border-purple-500 pl-8">{analysis.executive_summary}</p>
171
  </div>
172
  </div>
173
 
174
  <div className="grid gap-6 md:grid-cols-2">
175
+ <div className="glass-card rounded-3xl p-8 bg-white/[0.01]">
176
+ <h4 className="text-[11px] font-bold uppercase tracking-widest text-amber-400 mb-6 flex items-center gap-2">
177
+ <span>⚠️</span> Legal Compliance Gaps
178
+ </h4>
179
  <ul className="space-y-4">
180
  {analysis.compliance_gaps.map((gap, i) => (
181
+ <li key={i} className="flex gap-4 text-sm text-slate-400 leading-relaxed">
182
+ <span className="text-amber-500 font-bold"></span> {gap}
183
  </li>
184
  ))}
185
  </ul>
186
  </div>
187
+ <div className="glass-card rounded-3xl p-8 bg-white/[0.01]">
188
+ <h4 className="text-[11px] font-bold uppercase tracking-widest text-cyan mb-6 flex items-center gap-2">
189
+ <span>💎</span> Technical Requirements
190
+ </h4>
191
  <ul className="space-y-4">
192
  {analysis.key_requirements.map((req, i) => (
193
+ <li key={i} className="flex gap-4 text-sm text-slate-400 leading-relaxed">
194
+ <span className="text-cyan font-bold">▹</span> {req}
195
  </li>
196
  ))}
197
  </ul>
198
  </div>
199
  </div>
200
 
201
+ <div className="glass-card rounded-3xl p-10 bg-white/[0.01]">
202
+ <h4 className="text-[11px] font-bold uppercase tracking-widest text-purple-400 mb-8 text-center">Neural Risk Matrix</h4>
203
+ <div className="grid gap-6 md:grid-cols-2">
204
  {analysis.risks.map((risk, i) => (
205
+ <div key={i} className="group rounded-3xl bg-white/[0.02] p-6 border border-white/5 hover:border-purple-500/30 transition-all duration-300">
206
+ <div className="flex items-center justify-between mb-4">
207
+ <span className="font-bold text-white text-lg group-hover:text-purple-400 transition">{risk.title}</span>
208
+ <span className={`text-[9px] font-black px-3 py-1 rounded-full uppercase tracking-widest ${risk.severity === 'High' ? 'bg-red-500/20 text-red-500 border border-red-500/20' : 'bg-white/5 text-slate-500 border border-white/5'}`}>{risk.severity}</span>
209
  </div>
210
  <p className="text-xs text-slate-500 leading-relaxed">{risk.explanation}</p>
211
  </div>
 
214
  </div>
215
  </div>
216
 
217
+ <div className="lg:col-span-4">
218
+ <div className="glass-card rounded-3xl p-8 bg-black/40 h-full sticky top-32">
219
+ <div className="flex items-center gap-3 mb-8 border-b border-white/5 pb-6">
220
+ <div className="h-2 w-2 rounded-full bg-purple-500 animate-pulse shadow-[0_0_12px_rgba(168,85,247,0.8)]" />
221
+ <h4 className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Agent Intelligence Log</h4>
 
222
  </div>
223
+ <div className="space-y-8 overflow-y-auto max-h-[700px] pr-2 custom-scrollbar">
224
  {analysis.audit_log?.map((log, i) => (
225
+ <div key={i} className="flex gap-5 group">
226
  <div className="flex flex-col items-center">
227
+ <div className="h-8 w-8 rounded-xl bg-white/5 flex items-center justify-center text-sm border border-white/10 group-hover:border-purple-500/50 transition-all duration-300 shadow-lg">🤖</div>
228
+ {i < (analysis.audit_log?.length ?? 0) - 1 && <div className="w-px flex-1 bg-gradient-to-b from-purple-500/40 to-transparent my-3" />}
229
  </div>
230
+ <div className="pb-6">
231
+ <p className="text-[13px] text-slate-400 leading-relaxed group-hover:text-white transition-colors duration-300">{log}</p>
232
  </div>
233
  </div>
234
  ))}
 
237
  </div>
238
  </div>
239
  )}
 
 
 
 
 
 
 
 
240
  </div>
241
  );
242
  }
frontend/components/AnalysisHistory.tsx CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import type { AnalysisHistoryItem } from "../lib/types";
2
 
3
  type Props = {
@@ -7,62 +9,69 @@ type Props = {
7
  export default function AnalysisHistory({ history }: Props) {
8
  if (!history.length) {
9
  return (
10
- <div className="rounded-3xl border border-slate-800 bg-slate-950/80 p-8 text-slate-400">
11
- No hay historial de análisis todavía. Ejecuta un análisis para comenzar a guardarlo.
 
 
12
  </div>
13
  );
14
  }
15
 
16
  return (
17
- <div className="space-y-6">
18
- <div className="rounded-3xl border border-slate-800 bg-slate-950/80 p-6">
19
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
20
- <div>
21
- <h2 className="text-2xl font-semibold text-white">Analysis History</h2>
22
- <p className="mt-2 text-slate-400">Revisa los análisis previos generados por el agente para auditoría y seguimiento.</p>
23
- </div>
24
- </div>
25
- </div>
26
-
27
- <div className="space-y-4">
28
  {history.map((item) => (
29
- <div key={`${item.tender_code}-${item.analyzed_at}`} className="rounded-3xl border border-slate-800 bg-slate-900/80 p-6">
30
- <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
31
- <div>
32
- <div className="text-sm text-slate-500">Tender</div>
33
- <div className="text-lg font-semibold text-white">{item.tender_name}</div>
34
- <div className="text-sm text-slate-400">Código: {item.tender_code}</div>
 
 
 
35
  </div>
36
- <div className="text-right">
37
- <div className="text-sm uppercase tracking-[0.25em] text-cyan-300/80">Fit score</div>
38
- <div className="mt-2 text-3xl font-semibold text-white">{item.analysis.fit_score}%</div>
39
- <div className="mt-1 text-sm text-slate-400">{new Date(item.analyzed_at).toLocaleString()}</div>
 
 
 
 
 
 
 
 
 
40
  </div>
41
  </div>
42
- <div className="mt-6 grid gap-4 md:grid-cols-3">
43
- <div className="rounded-2xl bg-slate-950/80 p-4 text-sm text-slate-300">
44
- <div className="font-semibold text-white">Decision</div>
45
- <div className="mt-2">{item.analysis.decision}</div>
 
46
  </div>
47
- <div className="rounded-2xl bg-slate-950/80 p-4 text-sm text-slate-300">
48
- <div className="font-semibold text-white">Key Requirements</div>
49
- <div className="mt-2">{item.analysis.key_requirements.length}</div>
50
  </div>
51
- <div className="rounded-2xl bg-slate-950/80 p-4 text-sm text-slate-300">
52
- <div className="font-semibold text-white">Risks</div>
53
- <div className="mt-2">{item.analysis.risks.length}</div>
54
  </div>
55
- </div>
56
- {item.analysis.audit_log?.length ? (
57
- <div className="mt-6 rounded-2xl border border-amber-900/40 bg-amber-950/10 p-4 text-sm text-slate-200">
58
- <div className="font-semibold text-amber-200">Audit trail</div>
59
- <ul className="mt-3 list-disc space-y-2 pl-5">
60
- {item.analysis.audit_log.map((entry, index) => (
61
- <li key={index}>{entry}</li>
62
- ))}
63
- </ul>
64
  </div>
65
- ) : null}
 
 
 
 
 
 
 
66
  </div>
67
  ))}
68
  </div>
 
1
+ "use client";
2
+
3
  import type { AnalysisHistoryItem } from "../lib/types";
4
 
5
  type Props = {
 
9
  export default function AnalysisHistory({ history }: Props) {
10
  if (!history.length) {
11
  return (
12
+ <div className="flex flex-col items-center justify-center min-h-[40vh] glass-card rounded-3xl border border-white/5 p-12 text-center animate-in fade-in duration-700">
13
+ <div className="text-4xl mb-4 opacity-30">📜</div>
14
+ <h3 className="text-xl font-bold text-white mb-2">No Analysis History</h3>
15
+ <p className="text-slate-500 max-w-xs mx-auto">Your past agentic debates and reports will appear here for audit and review.</p>
16
  </div>
17
  );
18
  }
19
 
20
  return (
21
+ <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
22
+ <div className="space-y-6">
 
 
 
 
 
 
 
 
 
23
  {history.map((item) => (
24
+ <div key={`${item.tender_code}-${item.analyzed_at}`} className="glass-card rounded-3xl p-8 border border-white/5 hover:border-purple-500/30 transition-all duration-300">
25
+ <div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
26
+ <div className="space-y-1">
27
+ <div className="flex items-center gap-3 mb-2">
28
+ <span className="text-[10px] font-black uppercase tracking-widest text-purple-400 bg-purple-500/10 px-2 py-0.5 rounded border border-purple-500/20">Audit Record</span>
29
+ <span className="text-xs text-slate-500 font-mono">{item.tender_code}</span>
30
+ </div>
31
+ <h3 className="text-2xl font-bold text-white tracking-tight">{item.tender_name}</h3>
32
+ <p className="text-xs text-slate-500">{new Date(item.analyzed_at).toLocaleString()}</p>
33
  </div>
34
+
35
+ <div className="flex items-center gap-8 bg-white/5 rounded-2xl p-6 border border-white/5">
36
+ <div className="text-center">
37
+ <p className="text-[9px] uppercase tracking-widest text-slate-500 font-bold mb-1">Fit Score</p>
38
+ <p className="text-3xl font-black text-white">{item.analysis.fit_score}%</p>
39
+ </div>
40
+ <div className="h-10 w-px bg-white/10" />
41
+ <div className="text-center">
42
+ <p className="text-[9px] uppercase tracking-widest text-slate-500 font-bold mb-1">Decision</p>
43
+ <p className={`text-xs font-black uppercase tracking-widest px-3 py-1 rounded-full ${item.analysis.decision === 'Recommended' ? 'bg-green-500/20 text-green-400' : 'bg-amber-500/20 text-amber-400'}`}>
44
+ {item.analysis.decision}
45
+ </p>
46
+ </div>
47
  </div>
48
  </div>
49
+
50
+ <div className="mt-8 grid gap-4 grid-cols-2 md:grid-cols-4">
51
+ <div className="p-4 rounded-2xl bg-black/20 border border-white/5">
52
+ <p className="text-[9px] uppercase text-slate-500 font-bold mb-1">Risks Detected</p>
53
+ <p className="text-sm font-bold text-white">{item.analysis.risks.length}</p>
54
  </div>
55
+ <div className="p-4 rounded-2xl bg-black/20 border border-white/5">
56
+ <p className="text-[9px] uppercase text-slate-500 font-bold mb-1">Key Requirements</p>
57
+ <p className="text-sm font-bold text-white">{item.analysis.key_requirements.length}</p>
58
  </div>
59
+ <div className="p-4 rounded-2xl bg-black/20 border border-white/5">
60
+ <p className="text-[9px] uppercase text-slate-500 font-bold mb-1">Legal Gaps</p>
61
+ <p className="text-sm font-bold text-white">{item.analysis.compliance_gaps.length}</p>
62
  </div>
63
+ <div className="p-4 rounded-2xl bg-black/20 border border-white/5">
64
+ <p className="text-[9px] uppercase text-slate-500 font-bold mb-1">Audit Logs</p>
65
+ <p className="text-sm font-bold text-white">{item.analysis.audit_log?.length ?? 0}</p>
 
 
 
 
 
 
66
  </div>
67
+ </div>
68
+
69
+ <div className="mt-6">
70
+ <button className="text-xs font-bold text-purple-400 hover:text-white transition-colors flex items-center gap-2">
71
+ <span>View Full Analysis Details</span>
72
+ <span>→</span>
73
+ </button>
74
+ </div>
75
  </div>
76
  ))}
77
  </div>
frontend/components/GlobalSync.tsx ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ export default function GlobalSync({ onComplete }: { onComplete: () => void }) {
6
+ const [progress, setProgress] = useState(0);
7
+ const [message, setMessage] = useState("Initializing Neural Sync...");
8
+
9
+ const messages = [
10
+ "Establishing encrypted connection...",
11
+ "Synchronizing with Mercado Público...",
12
+ "Activating Legal Analyst Agent...",
13
+ "Activating Technical Reviewer Agent...",
14
+ "Activating Commercial Strategist Agent...",
15
+ "Orchestrating multi-agent pipeline...",
16
+ "Ready for analysis."
17
+ ];
18
+
19
+ useEffect(() => {
20
+ let currentMsg = 0;
21
+ const interval = setInterval(() => {
22
+ setProgress(prev => {
23
+ if (prev >= 100) {
24
+ clearInterval(interval);
25
+ setTimeout(onComplete, 500);
26
+ return 100;
27
+ }
28
+
29
+ // Update message based on progress
30
+ const msgIdx = Math.floor((prev / 100) * messages.length);
31
+ if (msgIdx !== currentMsg && messages[msgIdx]) {
32
+ currentMsg = msgIdx;
33
+ setMessage(messages[msgIdx]);
34
+ }
35
+
36
+ return prev + 2;
37
+ });
38
+ }, 40);
39
+
40
+ return () => clearInterval(interval);
41
+ }, [onComplete]);
42
+
43
+ return (
44
+ <div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[#030303] text-white">
45
+ <div className="relative w-64 h-64 flex items-center justify-center mb-12">
46
+ {/* Animated Rings */}
47
+ <div className="absolute inset-0 border-2 border-purple-500/20 rounded-full animate-[spin_10s_linear_infinite]" />
48
+ <div className="absolute inset-4 border-2 border-indigo-500/40 rounded-full animate-[spin_7s_linear_infinite_reverse]" />
49
+ <div className="absolute inset-8 border-2 border-cyan-500/60 rounded-full animate-[spin_5s_linear_infinite]" />
50
+
51
+ {/* Progress Text */}
52
+ <div className="flex flex-col items-center">
53
+ <span className="text-4xl font-bold text-gradient mb-1">{progress}%</span>
54
+ <span className="text-[10px] uppercase tracking-[0.2em] text-slate-500 font-bold">Syncing</span>
55
+ </div>
56
+ </div>
57
+
58
+ <div className="space-y-4 text-center">
59
+ <h2 className="text-xl font-bold tracking-tight">{message}</h2>
60
+ <div className="w-64 h-1 bg-white/5 rounded-full overflow-hidden">
61
+ <div
62
+ className="h-full premium-gradient transition-all duration-300 ease-out"
63
+ style={{ width: `${progress}%` }}
64
+ />
65
+ </div>
66
+ </div>
67
+
68
+ <div className="absolute bottom-12 text-[10px] uppercase tracking-[0.4em] text-slate-700 font-bold">
69
+ AndesOps AI Framework v1.0
70
+ </div>
71
+ </div>
72
+ );
73
+ }
frontend_tunnel.txt ADDED
Binary file (96 Bytes). View file
 
frontend_tunnel_new.txt ADDED
Binary file (96 Bytes). View file
 
frontend_tunnel_v3.txt ADDED
Binary file (98 Bytes). View file
 
start.sh CHANGED
@@ -1,10 +1,17 @@
1
  #!/bin/bash
2
 
 
 
 
 
3
  # Start Backend
 
4
  cd /app/backend && uvicorn app.main:app --host 0.0.0.0 --port 8000 &
5
 
6
  # Start Frontend
 
7
  cd /app/frontend && npm run start -- -p 3000 &
8
 
9
  # Start Nginx
 
10
  nginx -g "daemon off;"
 
1
  #!/bin/bash
2
 
3
+ # Seed Database with sample data for demo
4
+ echo "Seeding database..."
5
+ cd /app/backend && python seed_db.py
6
+
7
  # Start Backend
8
+ echo "Starting Backend..."
9
  cd /app/backend && uvicorn app.main:app --host 0.0.0.0 --port 8000 &
10
 
11
  # Start Frontend
12
+ echo "Starting Frontend..."
13
  cd /app/frontend && npm run start -- -p 3000 &
14
 
15
  # Start Nginx
16
+ echo "Starting Nginx..."
17
  nginx -g "daemon off;"