From 44dacedd903e021f1830a5e1cb563e657ae6ca52 Mon Sep 17 00:00:00 2001 From: jonaspachecoometas Date: Tue, 10 Mar 2026 18:28:14 -0300 Subject: [PATCH] =?UTF-8?q?feat(valuation):=20M=C3=B3dulo=20Consultivo=20d?= =?UTF-8?q?e=20Valuation=20completo=20-=20Motor=20DCF/M=C3=BAltiplos/Ativo?= =?UTF-8?q?s,=20Governan=C3=A7a=2020=20crit=C3=A9rios,=20SWOT,=20Canvas=20?= =?UTF-8?q?Dual,=20PDCA,=20BI=20Dashboard,=20Relat=C3=B3rios,=20Agente=20I?= =?UTF-8?q?A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Valuation.tsx | 2841 ++++++++++++------------- server/integrations/github/service.ts | 4 +- server/valuation/constants.ts | 131 ++ server/valuation/engine.ts | 484 +++++ server/valuation/routes.ts | 924 ++++++++ server/valuation/storage.ts | 236 ++ shared/schema.ts | 165 +- 7 files changed, 3324 insertions(+), 1461 deletions(-) create mode 100644 server/valuation/constants.ts create mode 100644 server/valuation/engine.ts diff --git a/client/src/pages/Valuation.tsx b/client/src/pages/Valuation.tsx index 06c30ef..c0f4b44 100644 --- a/client/src/pages/Valuation.tsx +++ b/client/src/pages/Valuation.tsx @@ -4,354 +4,206 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, DialogDescription } from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { useState } from "react"; +import { useState, useMemo } from "react"; import { useAuth } from "@/hooks/use-auth"; import { useToast } from "@/hooks/use-toast"; -import { - Plus, Building2, TrendingUp, Calculator, PieChart, FileText, - BarChart3, DollarSign, Calendar, MoreHorizontal, Eye, Edit, Trash2, - ChevronRight, Users, Target, Briefcase, Bot, Send, Loader2, Upload, MessageSquare, - ClipboardCheck, CheckCircle2, Circle, AlertCircle, Sparkles, ChevronDown, Paperclip, Download, X, - Layers, RefreshCw, ThumbsUp, ThumbsDown, Lightbulb +import { + Plus, Building2, TrendingUp, Calculator, PieChart, FileText, + BarChart3, DollarSign, Calendar, MoreHorizontal, Trash2, + ChevronRight, Users, Target, Bot, Send, Loader2, Upload, + ClipboardCheck, CheckCircle2, Circle, AlertCircle, Sparkles, ChevronDown, Download, X, + Layers, RefreshCw, Shield, Compass, Zap, ArrowUpRight, ArrowDownRight, + Grid3X3, Activity, Award, Flame, Search } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Progress } from "@/components/ui/progress"; -import { RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer, Legend } from "recharts"; - -interface ValuationProject { - id: number; - tenantId: number; - companyName: string; - cnpj?: string; - sector: string; - businessModel?: string; - stage: string; - size: string; - status?: string; - consultantId?: string; - clientUserId?: string; - clientId?: number; - valuationRangeMin?: string; - valuationRangeMax?: string; - finalValue?: string; - currency?: string; - reportUrl?: string; - notes?: string; - createdAt: string; - updatedAt: string; -} - -interface CrmClient { - id: number; - tenantId: number; - name: string; - tradeName?: string; - cnpj?: string; - email?: string; - phone?: string; - segment?: string; -} - -interface ValuationInput { - id: number; - projectId: number; - year: number; - isProjection?: number; - revenue?: string; - ebitda?: string; - netIncome?: string; - totalAssets?: string; - totalEquity?: string; -} - -interface ValuationCalculation { - id: number; - projectId: number; - method: string; - weight?: string; - enterpriseValue?: string; - equityValue?: string; - status?: string; - calculatedAt: string; -} - -interface ChecklistCategory { - id: number; - code: string; - name: string; - description?: string; - orderIndex: number; - icon?: string; - segmentFilter?: string; -} - -interface ChecklistItem { - id: number; - categoryId: number; - code: string; - title: string; - description?: string; - format?: string; - isRequired?: number; - orderIndex: number; - segmentFilter?: string; - agentPrompt?: string; -} - -interface ChecklistProgress { - id: number; - projectId: number; - itemId: number; - status: string; - notes?: string; - documentId?: number; - dataJson?: string; - agentAnalysis?: string; - completedAt?: string; - completedBy?: string; -} - -const sectors = [ - "Tecnologia", "Varejo", "Indústria", "Serviços", "Agronegócio", - "Saúde", "Educação", "Financeiro", "Imobiliário", "Logística" -]; +import { + RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, + ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, + PieChart as RePieChart, Pie, Cell, LineChart, Line, AreaChart, Area +} from "recharts"; +const sectors = ["Tecnologia", "Varejo", "Indústria", "Serviços", "Agronegócio", "Saúde", "Educação", "Financeiro", "Imobiliário", "Logística"]; const stages = ["Startup", "Growth", "Mature", "Turnaround", "Exit"]; const sizes = ["Micro", "Pequena", "Média", "Grande"]; -const statuses = ["draft", "in_progress", "review", "completed", "archived"]; +const PIE_COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"]; -function SectorAnalysisTab({ projectId, sector }: { projectId: number; sector: string }) { - const sectorBenchmarks: Record = { - "Tecnologia": { evEbitda: "15x - 25x", evRevenue: "3x - 8x", margin: "20% - 40%" }, - "Varejo": { evEbitda: "8x - 12x", evRevenue: "0.5x - 1.5x", margin: "3% - 8%" }, - "Indústria": { evEbitda: "6x - 10x", evRevenue: "0.8x - 2x", margin: "8% - 15%" }, - "Serviços": { evEbitda: "8x - 14x", evRevenue: "1x - 3x", margin: "10% - 20%" }, - "Agronegócio": { evEbitda: "5x - 8x", evRevenue: "0.5x - 1.5x", margin: "5% - 12%" }, - "Saúde": { evEbitda: "10x - 18x", evRevenue: "1.5x - 4x", margin: "10% - 25%" }, - "Educação": { evEbitda: "8x - 15x", evRevenue: "1x - 3x", margin: "15% - 30%" }, - "Financeiro": { evEbitda: "10x - 20x", evRevenue: "2x - 5x", margin: "20% - 35%" }, - "Imobiliário": { evEbitda: "8x - 14x", evRevenue: "1x - 3x", margin: "15% - 25%" }, - "Logística": { evEbitda: "7x - 12x", evRevenue: "0.8x - 2x", margin: "5% - 12%" }, - }; - const benchmark = sectorBenchmarks[sector] || { evEbitda: "8x - 12x", evRevenue: "1x - 2x", margin: "10% - 20%" }; - - return ( - -
- -

Análise Setorial - {sector}

-
-
- -

EV/EBITDA

-

{benchmark.evEbitda}

-
- -

EV/Receita

-

{benchmark.evRevenue}

-
- -

Margem EBITDA

-

{benchmark.margin}

-
-
-
- -

Dados de mercado e comparáveis setoriais

-

Conecte fontes de dados para análises avançadas

-
-
- ); +function fmt(v: string | number | null | undefined): string { + const n = typeof v === "string" ? parseFloat(v) : v; + if (!n && n !== 0) return "-"; + if (Math.abs(n) >= 1e6) return `R$ ${(n / 1e6).toFixed(2)}M`; + if (Math.abs(n) >= 1e3) return `R$ ${(n / 1e3).toFixed(1)}K`; + return `R$ ${n.toFixed(2)}`; } +function pct(v: string | number | null | undefined): string { + const n = typeof v === "string" ? parseFloat(v) : v; + if (!n && n !== 0) return "-"; + return `${n.toFixed(1)}%`; +} + +async function apiFetch(url: string, opts?: RequestInit) { + const res = await fetch(url, { credentials: "include", ...opts }); + if (!res.ok) throw new Error(`API error: ${res.status}`); + return res.json(); +} + +async function apiPost(url: string, body: any) { + return apiFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); +} + +async function apiPatch(url: string, body: any) { + return apiFetch(url, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); +} + +async function apiDelete(url: string) { + return apiFetch(url, { method: "DELETE" }); +} export default function Valuation() { const { user } = useAuth(); const { toast } = useToast(); const queryClient = useQueryClient(); - const [selectedProject, setSelectedProject] = useState(null); + const [selectedProject, setSelectedProject] = useState(null); const [newProjectOpen, setNewProjectOpen] = useState(false); const [activeTab, setActiveTab] = useState("overview"); - const [newInputOpen, setNewInputOpen] = useState(false); const [showAgentPanel, setShowAgentPanel] = useState(false); const [agentMessages, setAgentMessages] = useState<{ role: string; content: string }[]>([]); const [agentInput, setAgentInput] = useState(""); const [agentLoading, setAgentLoading] = useState(false); + const [newInputOpen, setNewInputOpen] = useState(false); + const [projectSearch, setProjectSearch] = useState(""); const [importOpen, setImportOpen] = useState(false); const [importStep, setImportStep] = useState<"upload" | "analyzing" | "review">("upload"); const [importFile, setImportFile] = useState(null); const [importedData, setImportedData] = useState([]); - const [importAnalysis, setImportAnalysis] = useState(""); const [expandedCategory, setExpandedCategory] = useState(null); - const [selectedChecklistItem, setSelectedChecklistItem] = useState(null); - const [checklistItemNotes, setChecklistItemNotes] = useState(""); - const [checklistAgentLoading, setChecklistAgentLoading] = useState(false); - const [uploadingItem, setUploadingItem] = useState(null); - const [itemAttachments, setItemAttachments] = useState>({}); - const [newInput, setNewInput] = useState({ - year: new Date().getFullYear(), - isProjection: 0, - revenue: "", - grossProfit: "", - ebitda: "", - ebit: "", - netIncome: "", - totalAssets: "", - totalLiabilities: "", - totalEquity: "", - cash: "", - debt: "", - }); - - const { data: projects = [], isLoading } = useQuery({ - queryKey: ["/api/valuation/projects"], - queryFn: async () => { - const res = await fetch("/api/valuation/projects", { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, - }); - - const { data: inputs = [] } = useQuery({ - queryKey: ["/api/valuation/projects", selectedProject?.id, "inputs"], - queryFn: async () => { - if (!selectedProject) return []; - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/inputs`, { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, - enabled: !!selectedProject, - }); - - const { data: calculations = [] } = useQuery({ - queryKey: ["/api/valuation/projects", selectedProject?.id, "calculations"], - queryFn: async () => { - if (!selectedProject) return []; - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/calculations`, { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, - enabled: !!selectedProject, - }); - - const { data: checklistCategories = [] } = useQuery({ - queryKey: ["/api/valuation/checklist/categories", selectedProject?.sector], - queryFn: async () => { - const segmentMap: Record = { - "Tecnologia": "technology", - "Fintech": "fintech", - "E-commerce": "ecommerce", - "Indústria": "industry", - "Agronegócio": "agro", - }; - const segment = selectedProject ? segmentMap[selectedProject.sector] || "" : ""; - const res = await fetch(`/api/valuation/checklist/categories?segment=${segment}`, { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, - enabled: !!selectedProject, - }); - - const { data: checklistItems = [] } = useQuery({ - queryKey: ["/api/valuation/checklist/items", selectedProject?.sector], - queryFn: async () => { - const segmentMap: Record = { - "Tecnologia": "technology", - "Fintech": "fintech", - "E-commerce": "ecommerce", - "Indústria": "industry", - "Agronegócio": "agro", - }; - const segment = selectedProject ? segmentMap[selectedProject.sector] || "" : ""; - const res = await fetch(`/api/valuation/checklist/items?segment=${segment}`, { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, - enabled: !!selectedProject, - }); - - const { data: checklistProgress = [], refetch: refetchProgress } = useQuery({ - queryKey: ["/api/valuation/projects", selectedProject?.id, "checklist"], - queryFn: async () => { - if (!selectedProject) return []; - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/checklist`, { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, - enabled: !!selectedProject, - }); + const [swotNewItem, setSwotNewItem] = useState({ quadrant: "strengths", item: "", impact: "medium" }); + const [pdcaNewItem, setPdcaNewItem] = useState({ title: "", originArea: "governance", priority: "medium", description: "" }); + const [assetNewItem, setAssetNewItem] = useState({ assetType: "physical", name: "", bookValue: "", marketValue: "" }); + const [reportGenerating, setReportGenerating] = useState(false); + const [reportContent, setReportContent] = useState(""); + const [showReport, setShowReport] = useState(false); + const [calculating, setCalculating] = useState(false); const [newProject, setNewProject] = useState({ - companyName: "", - cnpj: "", - sector: "", - businessModel: "", - stage: "Growth", - size: "Média", - notes: "", - clientId: null as number | null, - }); - const [clientSearch, setClientSearch] = useState(""); - const [showQualification, setShowQualification] = useState(false); - const [qualificationStep, setQualificationStep] = useState(0); - const [qualificationAnswers, setQualificationAnswers] = useState>({}); - - const qualificationQuestions = [ - { id: "objective", question: "Qual é o objetivo principal desta avaliação?", options: ["Venda da empresa", "Captação de investimento", "Fusão/Aquisição", "Planejamento sucessório", "Avaliação interna"] }, - { id: "urgency", question: "Qual a urgência para conclusão?", options: ["Imediata (30 dias)", "Curto prazo (90 dias)", "Médio prazo (6 meses)", "Sem pressa definida"] }, - { id: "data_readiness", question: "Como está a organização dos dados financeiros?", options: ["Totalmente organizados e auditados", "Organizados mas não auditados", "Parcialmente organizados", "Precisam ser levantados"] }, - { id: "previous_valuation", question: "Já foi realizado algum valuation anterior?", options: ["Sim, recentemente", "Sim, há mais de 2 anos", "Nunca foi feito"] }, - ]; - - const { data: crmClients = [] } = useQuery({ - queryKey: ["/api/valuation/crm-clients", clientSearch], - queryFn: async () => { - const params = clientSearch ? `?search=${encodeURIComponent(clientSearch)}` : ""; - const res = await fetch(`/api/valuation/crm-clients${params}`, { credentials: "include" }); - if (!res.ok) return []; - return res.json(); - }, + companyName: "", cnpj: "", sector: "", businessModel: "", stage: "Growth", + size: "Média", projectType: "simple", notes: "", clientId: null as number | null, }); - const createProjectMutation = useMutation({ - mutationFn: async (data: typeof newProject) => { - const res = await fetch("/api/valuation/projects", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - credentials: "include", - }); - if (!res.ok) throw new Error("Failed to create project"); - return res.json(); - }, - onSuccess: () => { + const [newInput, setNewInput] = useState({ + year: new Date().getFullYear(), isProjection: 0, revenue: "", grossProfit: "", + ebitda: "", ebit: "", netIncome: "", totalAssets: "", totalLiabilities: "", + totalEquity: "", cash: "", debt: "", freeCashFlow: "", capex: "", + }); + + const pid = selectedProject?.id; + const base = `/api/valuation/projects/${pid}`; + + const { data: projects = [] } = useQuery({ + queryKey: ["/api/valuation/projects"], + queryFn: () => apiFetch("/api/valuation/projects").catch(() => []), + }); + + const { data: inputs = [] } = useQuery({ + queryKey: ["val-inputs", pid], + queryFn: () => apiFetch(`${base}/inputs`), + enabled: !!pid, + }); + + const { data: summary } = useQuery({ + queryKey: ["val-summary", pid], + queryFn: () => apiFetch(`${base}/summary`).catch(() => null), + enabled: !!pid, + }); + + const { data: results = [] } = useQuery({ + queryKey: ["val-results", pid], + queryFn: () => apiFetch(`${base}/results`).catch(() => []), + enabled: !!pid, + }); + + const { data: governance = [] } = useQuery({ + queryKey: ["val-governance", pid], + queryFn: () => apiFetch(`${base}/governance`).catch(() => []), + enabled: !!pid, + }); + + const { data: swotItems = [] } = useQuery({ + queryKey: ["val-swot", pid], + queryFn: () => apiFetch(`${base}/swot`).catch(() => []), + enabled: !!pid, + }); + + const { data: pdcaItems = [] } = useQuery({ + queryKey: ["val-pdca", pid], + queryFn: () => apiFetch(`${base}/pdca`).catch(() => []), + enabled: !!pid, + }); + + const { data: assets = [] } = useQuery({ + queryKey: ["val-assets", pid], + queryFn: () => apiFetch(`${base}/assets`).catch(() => []), + enabled: !!pid, + }); + + const { data: aiLogs = [] } = useQuery({ + queryKey: ["val-ai-feed", pid], + queryFn: () => apiFetch(`${base}/ai-feed`).catch(() => []), + enabled: !!pid, + }); + + const { data: canvasBlocks = [] } = useQuery({ + queryKey: ["val-canvas", pid], + queryFn: () => apiFetch(`${base}/canvas`).catch(() => []), + enabled: !!pid, + }); + + const { data: checklistProgress = [], refetch: refetchProgress } = useQuery({ + queryKey: ["val-checklist", pid], + queryFn: () => apiFetch(`${base}/checklist`).catch(() => []), + enabled: !!pid, + }); + + const { data: checklistCategories = [] } = useQuery({ + queryKey: ["val-cl-cats"], + queryFn: () => apiFetch("/api/valuation/checklist/categories").catch(() => []), + enabled: !!pid, + }); + + const { data: checklistItems = [] } = useQuery({ + queryKey: ["val-cl-items"], + queryFn: () => apiFetch("/api/valuation/checklist/items").catch(() => []), + enabled: !!pid, + }); + + const filteredProjects = useMemo(() => { + if (!projectSearch) return projects; + const s = projectSearch.toLowerCase(); + return projects.filter((p: any) => p.companyName?.toLowerCase().includes(s) || p.sector?.toLowerCase().includes(s)); + }, [projects, projectSearch]); + + const inv = (...keys: string[]) => keys.forEach(k => queryClient.invalidateQueries({ queryKey: [k, pid] })); + const invAll = () => inv("val-inputs", "val-results", "val-summary", "val-governance", "val-swot", "val-pdca", "val-assets", "val-ai-feed", "val-canvas", "val-checklist"); + + const createProject = useMutation({ + mutationFn: (data: typeof newProject) => apiPost("/api/valuation/projects", data), + onSuccess: (p) => { queryClient.invalidateQueries({ queryKey: ["/api/valuation/projects"] }); setNewProjectOpen(false); - setNewProject({ companyName: "", cnpj: "", sector: "", businessModel: "", stage: "Growth", size: "Média", notes: "", clientId: null }); - setShowQualification(false); - setQualificationStep(0); - setQualificationAnswers({}); - toast({ title: "Projeto de valuation criado com sucesso" }); - }, - onError: () => { - toast({ title: "Erro ao criar projeto", variant: "destructive" }); + setNewProject({ companyName: "", cnpj: "", sector: "", businessModel: "", stage: "Growth", size: "Média", projectType: "simple", notes: "", clientId: null }); + toast({ title: "Projeto criado com sucesso" }); }, }); - const deleteProjectMutation = useMutation({ - mutationFn: async (id: number) => { - const res = await fetch(`/api/valuation/projects/${id}`, { - method: "DELETE", - credentials: "include", - }); - if (!res.ok) throw new Error("Failed to delete project"); - }, + const deleteProject = useMutation({ + mutationFn: (id: number) => apiDelete(`/api/valuation/projects/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/valuation/projects"] }); setSelectedProject(null); @@ -359,1228 +211,1301 @@ export default function Valuation() { }, }); - const createInputMutation = useMutation({ - mutationFn: async (data: typeof newInput) => { - if (!selectedProject) throw new Error("No project selected"); - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/inputs`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - credentials: "include", - }); - if (!res.ok) throw new Error("Failed to create input"); - return res.json(); - }, + const createInput = useMutation({ + mutationFn: (data: typeof newInput) => apiPost(`${base}/inputs`, data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["/api/valuation/projects", selectedProject?.id, "inputs"] }); + inv("val-inputs", "val-summary"); setNewInputOpen(false); - setNewInput({ year: new Date().getFullYear(), isProjection: 0, revenue: "", grossProfit: "", ebitda: "", ebit: "", netIncome: "", totalAssets: "", totalLiabilities: "", totalEquity: "", cash: "", debt: "" }); toast({ title: "Dados financeiros adicionados" }); }, - onError: () => { - toast({ title: "Erro ao adicionar dados", variant: "destructive" }); - }, }); - const sendAgentMessage = async () => { - if (!agentInput.trim() || !selectedProject) return; - - const userMessage = { role: "user", content: agentInput }; - setAgentMessages(prev => [...prev, userMessage]); - setAgentInput(""); - setAgentLoading(true); - + const runCalculation = async () => { + if (!pid) return; + setCalculating(true); try { - const context = `Você está auxiliando na avaliação da empresa "${selectedProject.companyName}" (setor: ${selectedProject.sector}, porte: ${selectedProject.size}, estágio: ${selectedProject.stage}). Dados disponíveis: ${inputs.length} anos de dados financeiros.`; - - const res = await fetch("/api/agent/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - message: agentInput, - context: context, - module: "valuation" - }), - credentials: "include", - }); - - if (res.ok) { - const data = await res.json(); - setAgentMessages(prev => [...prev, { role: "assistant", content: data.response || data.message || "Desculpe, não consegui processar sua solicitação." }]); - } else { - setAgentMessages(prev => [...prev, { role: "assistant", content: "Erro ao conectar com o assistente. Tente novamente." }]); - } - } catch (error) { - setAgentMessages(prev => [...prev, { role: "assistant", content: "Erro de conexão. Verifique sua internet." }]); - } finally { - setAgentLoading(false); - } + await apiPost(`${base}/calculate`, {}); + invAll(); + toast({ title: "Cálculo de valuation concluído" }); + } catch { toast({ title: "Erro no cálculo", variant: "destructive" }); } + finally { setCalculating(false); } }; - const handleFileUpload = async (e: React.ChangeEvent) => { + const initGovernance = async () => { + if (!pid) return; + try { + await apiPost(`${base}/governance/initialize`, {}); + inv("val-governance"); + toast({ title: "Governança inicializada com 20 critérios" }); + } catch { toast({ title: "Erro ao inicializar governança", variant: "destructive" }); } + }; + + const updateGovScore = async (criterionId: number, currentScore: number) => { + try { + await apiPatch(`${base}/governance/${criterionId}`, { currentScore }); + inv("val-governance", "val-summary"); + } catch { toast({ title: "Erro ao atualizar", variant: "destructive" }); } + }; + + const generateSwot = async () => { + if (!pid) return; + try { + await apiPost(`${base}/swot/generate`, {}); + inv("val-swot", "val-ai-feed"); + toast({ title: "SWOT gerado com IA" }); + } catch { toast({ title: "Erro ao gerar SWOT", variant: "destructive" }); } + }; + + const addSwotItem = async () => { + if (!pid || !swotNewItem.item) return; + try { + await apiPost(`${base}/swot`, swotNewItem); + inv("val-swot"); + setSwotNewItem({ quadrant: "strengths", item: "", impact: "medium" }); + } catch { toast({ title: "Erro ao adicionar", variant: "destructive" }); } + }; + + const deleteSwotItem = async (itemId: number) => { + try { + await apiDelete(`${base}/swot/${itemId}`); + inv("val-swot"); + } catch {} + }; + + const addPdcaItem = async () => { + if (!pid || !pdcaNewItem.title) return; + try { + await apiPost(`${base}/pdca`, pdcaNewItem); + inv("val-pdca"); + setPdcaNewItem({ title: "", originArea: "governance", priority: "medium", description: "" }); + } catch { toast({ title: "Erro ao adicionar", variant: "destructive" }); } + }; + + const updatePdcaPhase = async (itemId: number, phase: string) => { + try { + await apiPatch(`${base}/pdca/${itemId}`, { phase }); + inv("val-pdca"); + } catch {} + }; + + const deletePdcaItem = async (itemId: number) => { + try { + await apiDelete(`${base}/pdca/${itemId}`); + inv("val-pdca"); + } catch {} + }; + + const addAsset = async () => { + if (!pid || !assetNewItem.name) return; + try { + await apiPost(`${base}/assets`, assetNewItem); + inv("val-assets"); + setAssetNewItem({ assetType: "physical", name: "", bookValue: "", marketValue: "" }); + } catch { toast({ title: "Erro ao adicionar", variant: "destructive" }); } + }; + + const deleteAsset = async (id: number) => { + try { + await apiDelete(`${base}/assets/${id}`); + inv("val-assets"); + } catch {} + }; + + const generateReport = async (type: string) => { + if (!pid) return; + setReportGenerating(true); + try { + const data = await apiPost(`${base}/reports/generate`, { reportType: type, format: "html" }); + setReportContent(data.content || ""); + setShowReport(true); + toast({ title: "Relatório gerado" }); + } catch { toast({ title: "Erro ao gerar relatório", variant: "destructive" }); } + finally { setReportGenerating(false); } + }; + + const sendAgentMessage = async () => { + if (!agentInput.trim() || !pid) return; + setAgentMessages(prev => [...prev, { role: "user", content: agentInput }]); + setAgentInput(""); + setAgentLoading(true); + try { + const data = await apiPost(`${base}/ai-chat`, { message: agentInput }); + setAgentMessages(prev => [...prev, { role: "assistant", content: data.reply || "Sem resposta" }]); + inv("val-ai-feed"); + } catch { + setAgentMessages(prev => [...prev, { role: "assistant", content: "Erro ao conectar com o assistente." }]); + } finally { setAgentLoading(false); } + }; + + const initChecklist = async () => { + if (!pid) return; + try { + await apiFetch(`${base}/checklist/initialize`, { method: "POST" }); + refetchProgress(); + toast({ title: "Checklist inicializado" }); + } catch { toast({ title: "Erro ao inicializar", variant: "destructive" }); } + }; + + const updateChecklistItem = async (itemId: number, status: string) => { + try { + await apiFetch(`${base}/checklist/${itemId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status }), + }); + refetchProgress(); + } catch {} + }; + + const generateProjections = async () => { + if (!pid) return; + try { + await apiPost(`${base}/projections`, { years: 5 }); + inv("val-inputs", "val-summary"); + toast({ title: "Projeções geradas (5 anos)" }); + } catch { toast({ title: "Erro ao gerar projeções", variant: "destructive" }); } + }; + + const handleImportUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; - if (!file || !selectedProject) return; - + if (!file || !pid) return; setImportFile(file); setImportStep("analyzing"); - const formData = new FormData(); formData.append("file", file); - try { - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/import-financial`, { - method: "POST", - body: formData, - credentials: "include", - }); - + const res = await fetch(`${base}/import-financial`, { method: "POST", body: formData, credentials: "include" }); if (res.ok) { const data = await res.json(); setImportedData(data.rows || []); - setImportAnalysis(data.analysis || ""); setImportStep("review"); - } else { - toast({ title: "Erro ao processar arquivo", variant: "destructive" }); - setImportStep("upload"); - } - } catch (error) { - toast({ title: "Erro ao enviar arquivo", variant: "destructive" }); - setImportStep("upload"); - } + } else { setImportStep("upload"); toast({ title: "Erro ao processar", variant: "destructive" }); } + } catch { setImportStep("upload"); } }; const saveImportedData = async () => { - if (!selectedProject || importedData.length === 0) return; - - try { - for (const row of importedData) { - await fetch(`/api/valuation/projects/${selectedProject.id}/inputs`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(row), - credentials: "include", - }); - } - queryClient.invalidateQueries({ queryKey: ["/api/valuation/projects", selectedProject.id, "inputs"] }); - setImportOpen(false); - setImportStep("upload"); - setImportFile(null); - setImportedData([]); - setImportAnalysis(""); - toast({ title: `${importedData.length} anos de dados importados com sucesso` }); - } catch (error) { - toast({ title: "Erro ao salvar dados", variant: "destructive" }); + if (!pid || !importedData.length) return; + for (const row of importedData) { + await apiPost(`${base}/inputs`, row); } + inv("val-inputs", "val-summary"); + setImportOpen(false); + setImportStep("upload"); + setImportedData([]); + toast({ title: `${importedData.length} anos importados` }); }; - const initializeChecklist = async () => { - if (!selectedProject) return; - try { - await fetch(`/api/valuation/projects/${selectedProject.id}/checklist/initialize`, { - method: "POST", - credentials: "include", - }); - refetchProgress(); - toast({ title: "Checklist inicializado com sucesso" }); - } catch (error) { - toast({ title: "Erro ao inicializar checklist", variant: "destructive" }); - } - }; + const govCategories = useMemo(() => { + const cats: Record = {}; + governance.forEach((g: any) => { + if (!cats[g.category]) cats[g.category] = []; + cats[g.category].push(g); + }); + return cats; + }, [governance]); - const updateChecklistItem = async (itemId: number, status: string, notes?: string) => { - if (!selectedProject) return; - try { - await fetch(`/api/valuation/projects/${selectedProject.id}/checklist/${itemId}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ status, notes }), - credentials: "include", - }); - refetchProgress(); - } catch (error) { - toast({ title: "Erro ao atualizar item", variant: "destructive" }); - } - }; + const govRadarData = useMemo(() => { + return Object.entries(govCategories).map(([cat, items]) => { + const avg = items.reduce((s: number, i: any) => s + (i.currentScore || 0), 0) / items.length; + const avgTarget = items.reduce((s: number, i: any) => s + (i.targetScore || 10), 0) / items.length; + return { category: cat, current: avg, target: avgTarget }; + }); + }, [govCategories]); - const requestAgentAssist = async (item: ChecklistItem) => { - if (!selectedProject) return; - setChecklistAgentLoading(true); - try { - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/checklist/${item.id}/agent-assist`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ content: checklistItemNotes }), - credentials: "include", - }); - if (res.ok) { - const data = await res.json(); - refetchProgress(); - toast({ title: "Análise do Agent concluída" }); - } - } catch (error) { - toast({ title: "Erro ao solicitar assistência do Agent", variant: "destructive" }); - } finally { - setChecklistAgentLoading(false); - } - }; + const historicalInputs = useMemo(() => inputs.filter((i: any) => !i.isProjection).sort((a: any, b: any) => a.year - b.year), [inputs]); + const projectedInputs = useMemo(() => inputs.filter((i: any) => i.isProjection).sort((a: any, b: any) => a.year - b.year), [inputs]); - const loadAttachments = async (itemId: number) => { - if (!selectedProject) return; - try { - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/checklist/${itemId}/attachments`, { - credentials: "include", - }); - if (res.ok) { - const data = await res.json(); - setItemAttachments(prev => ({ ...prev, [itemId]: data })); - } - } catch (error) { - console.error("Error loading attachments:", error); - } - }; + const financialChartData = useMemo(() => { + return inputs.sort((a: any, b: any) => a.year - b.year).map((i: any) => ({ + year: i.year, + receita: parseFloat(i.revenue || "0") / 1e6, + ebitda: parseFloat(i.ebitda || "0") / 1e6, + lucro: parseFloat(i.netIncome || "0") / 1e6, + fcf: parseFloat(i.freeCashFlow || "0") / 1e6, + isProjection: !!i.isProjection, + })); + }, [inputs]); - const uploadAttachment = async (itemId: number, file: File) => { - if (!selectedProject) return; - setUploadingItem(itemId); - try { - const formData = new FormData(); - formData.append("file", file); - - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/checklist/${itemId}/attachments`, { - method: "POST", - body: formData, - credentials: "include", - }); - - if (res.ok) { - toast({ title: "Arquivo anexado com sucesso" }); - loadAttachments(itemId); - refetchProgress(); - } else { - const error = await res.json(); - toast({ title: error.error || "Erro ao anexar arquivo", variant: "destructive" }); - } - } catch (error) { - toast({ title: "Erro ao anexar arquivo", variant: "destructive" }); - } finally { - setUploadingItem(null); - } - }; + const baseResults = useMemo(() => results.filter((r: any) => r.scenario === "base"), [results]); - const deleteAttachment = async (itemId: number, attachmentId: number) => { - if (!selectedProject) return; - try { - const res = await fetch(`/api/valuation/projects/${selectedProject.id}/checklist/${itemId}/attachments/${attachmentId}`, { - method: "DELETE", - credentials: "include", - }); - - if (res.ok) { - toast({ title: "Anexo removido" }); - loadAttachments(itemId); - } - } catch (error) { - toast({ title: "Erro ao remover anexo", variant: "destructive" }); - } - }; + const methodPieData = useMemo(() => { + return baseResults.map((r: any) => ({ + name: { dcf: "DCF", ev_ebitda: "EV/EBITDA", ev_revenue: "EV/Receita", patrimonial: "Patrimonial", assets: "Ativos" }[r.method] || r.method, + value: parseFloat(r.enterpriseValue || "0") / 1e6, + })); + }, [baseResults]); - const getItemProgress = (itemId: number): ChecklistProgress | undefined => { - return checklistProgress.find(p => p.itemId === itemId); - }; + const scenarioBarData = useMemo(() => { + const scenarios: Record = {}; + results.forEach((r: any) => { + if (!scenarios[r.scenario]) scenarios[r.scenario] = 0; + scenarios[r.scenario] += parseFloat(r.enterpriseValue || "0") * parseFloat(r.weight || "0"); + }); + return Object.entries(scenarios).map(([s, v]) => ({ + scenario: { conservative: "Conservador", base: "Base", optimistic: "Otimista" }[s] || s, + valor: v / 1e6, + })); + }, [results]); - const getCategoryProgress = (categoryId: number) => { - const categoryItems = checklistItems.filter(i => i.categoryId === categoryId); - const completed = categoryItems.filter(i => getItemProgress(i.id)?.status === "completed").length; - return { total: categoryItems.length, completed }; - }; - - const getOverallProgress = () => { - if (checklistItems.length === 0) return 0; - const completed = checklistItems.filter(i => getItemProgress(i.id)?.status === "completed").length; - return Math.round((completed / checklistItems.length) * 100); - }; - - const statusColors: Record = { - draft: "bg-slate-100 text-slate-600", - in_progress: "bg-blue-100 text-blue-600", - review: "bg-yellow-100 text-yellow-700", - completed: "bg-green-100 text-green-600", - archived: "bg-gray-100 text-gray-500", - }; - - const statusLabels: Record = { - draft: "Rascunho", - in_progress: "Em Andamento", - review: "Em Revisão", - completed: "Concluído", - archived: "Arquivado", - }; - - const formatCurrency = (value?: string) => { - if (!value) return "-"; - const num = parseFloat(value); - return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(num); - }; - - const getProgressPercentage = (status?: string) => { - const statusProgress: Record = { - draft: 10, - in_progress: 50, - review: 80, - completed: 100, - archived: 100, - }; - return statusProgress[status || "draft"] || 0; - }; + const pdcaHeatMap = useMemo(() => { + const areas = ["governance", "financial", "operational", "commercial", "hr", "technology", "legal", "esg"]; + const priorities = ["critical", "high", "medium", "low"]; + const map: Record = {}; + pdcaItems.forEach((p: any) => { + const key = `${p.originArea}-${p.priority}`; + map[key] = (map[key] || 0) + 1; + }); + return { areas, priorities, map }; + }, [pdcaItems]); return ( - -
- - -
- {selectedProject ? ( - <> -
+
+ + setProjectSearch(e.target.value)} data-testid="input-project-search" /> +
+
+ + {filteredProjects.map((p: any) => ( +
{ setSelectedProject(p); setActiveTab("overview"); }} + data-testid={`card-project-${p.id}`} + >
-
-
- -

{selectedProject.companyName}

- - {statusLabels[selectedProject.status || "draft"]} - -
-

- {selectedProject.sector} | {selectedProject.stage} | {selectedProject.size} -

-
-
-
-

Valor Estimado

-

- {selectedProject.finalValue ? formatCurrency(selectedProject.finalValue) : - selectedProject.valuationRangeMin && selectedProject.valuationRangeMax ? - `${formatCurrency(selectedProject.valuationRangeMin)} - ${formatCurrency(selectedProject.valuationRangeMax)}` : - "A calcular"} -

-
-
-

Progresso

- -
-
+ {p.companyName} + {p.projectType === "governance" ? "Gov" : "Simples"} +
+
+ {p.sector} + | + {p.size} +
+ {p.currentValuation && ( +

{fmt(p.currentValuation)}

+ )} +
+ ))} + {filteredProjects.length === 0 && ( +
Nenhum projeto encontrado
+ )} +
+
+ + {/* Main Content */} +
+ {!selectedProject ? ( +
+
+ +

Módulo de Valuation

+

Selecione um projeto ou crie um novo para começar

+ +
+
+ ) : ( + <> +
+
+

{selectedProject.companyName}

+

{selectedProject.sector} | {selectedProject.size} | {selectedProject.stage}

+
+
+ + + + + + + + generateReport("executive")} data-testid="menu-report-executive">Relatório Executivo + generateReport("technical")} data-testid="menu-report-technical">Relatório Técnico + deleteProject.mutate(selectedProject.id)} data-testid="menu-delete-project">Excluir Projeto + +
- - - - Visão Geral - - - Checklist - - - Dados Financeiros - - - Cálculos - - - Cap Table - - - Data Room - - - Análise Setorial - - +
+
+ + + Visão Geral + Checklist + Financeiro + Governança + SWOT + Canvas + PDCA + Análise + Ativos + Documentos + -
- -
- -
-
- -
-
-

Enterprise Value

-

- {calculations.length > 0 - ? formatCurrency(calculations[0].enterpriseValue) - : "-"} + + {/* ===== OVERVIEW TAB ===== */} + +

+ +
+ + Progresso +
+

{summary?.checklist?.progress || 0}%

+ +

{summary?.checklist?.completed || 0}/{summary?.checklist?.total || 0} itens

+
+ +
+ + Valuation Atual +
+

{fmt(summary?.valuation?.currentEV)}

+

Enterprise Value

+
+ +
+ + Valuation Projetado +
+

{fmt(summary?.valuation?.projectedEV)}

+

Com melhorias de governança

+
+ +
+ + Criação de Valor +
+

+ {summary?.valuation?.creationPct ? `+${summary.valuation.creationPct}%` : "-"}

-
+

{fmt(summary?.valuation?.creationOfValue)}

+
- - -
-
- -
-
-

Equity Value

-

- {calculations.length > 0 - ? formatCurrency(calculations[0].equityValue) - : "-"} -

-
-
-
- - -
-
- -
-
-

Metodologias

-

{calculations.length}

-
-
-
-
- -
- -

- Informações da Empresa -

-
-
- CNPJ: - {selectedProject.cnpj || "-"} -
-
- Setor: - {selectedProject.sector} -
-
- Modelo: - {selectedProject.businessModel || "-"} -
-
- Estágio: - {selectedProject.stage} -
-
- Porte: - {selectedProject.size} -
-
-
- - -

- Últimos Resultados -

- {inputs.length > 0 ? ( -
- {inputs.slice(0, 3).map((input) => ( -
- {input.year}: - Receita: {formatCurrency(input.revenue)} +
+ +

Governança

+ {summary?.governance ? ( +
+
+ Score Atual + {summary.governance.currentScore}/10 +
+
+ Score Projetado + {summary.governance.projectedScore}/10 +
+
+ Uplift Potencial + +{summary.governance.uplift}% +
+
+ Redução WACC + -{summary.governance.waccReduction}% +
- ))} -
- ) : ( -

Nenhum dado financeiro cadastrado

- )} - -
+ ) : ( +
+

Governança não inicializada

+ +
+ )} + - {selectedProject.notes && ( - -

Observações

-

{selectedProject.notes}

-
- )} - - - -
-
-
-

Checklist de Dados para Valuation

-

Complete os itens abaixo com assistência do Agent

+ +

Feed do Agente IA

+ {aiLogs.length > 0 ? ( +
+ {aiLogs.slice(0, 5).map((log: any) => ( +
+ + {log.eventType === "calculation" ? "Calc" : log.eventType === "chat" ? "Chat" : log.eventType === "swot_generation" ? "SWOT" : log.eventType} + + {log.outputSummary} +
+ ))} +
+ ) : ( +

Nenhuma ação registrada

+ )} +
-
-
-

{getOverallProgress()}%

-

Completo

+ +
+ +
+ + SWOT + {summary?.swot?.total || 0} +
+
+ +
+ + PDCA + {summary?.pdca?.completed || 0}/{summary?.pdca?.total || 0} +
+
+ +
+ + Ativos + {fmt(summary?.assets?.totalMarketValue)} +
+
+
+ + + {/* ===== CHECKLIST TAB ===== */} + +
+

Checklist de Documentos

+ +
+ {checklistCategories.length > 0 ? ( + checklistCategories.map((cat: any) => { + const catItems = checklistItems.filter((i: any) => i.categoryId === cat.id); + const completed = catItems.filter((i: any) => { + const prog = checklistProgress.find((p: any) => p.itemId === i.id); + return prog?.status === "uploaded" || prog?.status === "completed"; + }).length; + return ( + +
setExpandedCategory(expandedCategory === cat.id ? null : cat.id)}> +
+ + {cat.name} + {completed}/{catItems.length} +
+ 0 ? (completed / catItems.length) * 100 : 0} className="w-24" /> +
+ {expandedCategory === cat.id && ( +
+ {catItems.map((item: any) => { + const prog = checklistProgress.find((p: any) => p.itemId === item.id); + const st = prog?.status || "pending"; + return ( +
+
+ {st === "uploaded" || st === "completed" ? ( + + ) : st === "not_available" ? ( + + ) : ( + + )} + {item.title} +
+ +
+ ); + })} +
+ )} +
+ ); + }) + ) : ( + + +

Clique em "Inicializar" para criar o checklist

+
+ )} +
+ + {/* ===== FINANCIALS TAB ===== */} + +
+

Dados Financeiros

+
+ + +
- {checklistProgress.length === 0 && ( -
+ + {financialChartData.length > 0 && ( + +

Evolução Financeira (R$ Milhões)

+ + + + + + `R$ ${v.toFixed(2)}M`} /> + + + + + + +
+ )} + + +
+ + + + + + + + + + + + + + + {inputs.sort((a: any, b: any) => a.year - b.year).map((i: any) => ( + + + + + + + + + + + ))} + +
AnoTipoReceitaEBITDALucro LíquidoAtivosPLFCF
{i.year}{i.isProjection ? "Projeção" : "Histórico"}{fmt(i.revenue)}{fmt(i.ebitda)}{fmt(i.netIncome)}{fmt(i.totalAssets)}{fmt(i.totalEquity)}{fmt(i.freeCashFlow)}
+
+
+
+ + {/* ===== GOVERNANCE TAB ===== */} + +
+

Governança Corporativa

+ {governance.length === 0 && ( + )}
-
- + {governance.length > 0 && ( + <> +
+ +

Radar de Governança

+ + + + + + + + + + +
+ +

Impacto por Categoria

+ + + + + + + + + + +
+
-
- {checklistCategories.map((category) => { - const progress = getCategoryProgress(category.id); - const isExpanded = expandedCategory === category.id; - const categoryItems = checklistItems.filter(i => i.categoryId === category.id); - - return ( - -
{ - const newExpanded = isExpanded ? null : category.id; - setExpandedCategory(newExpanded); - if (newExpanded) { - categoryItems.forEach(i => loadAttachments(i.id)); - } - }} - data-testid={`button-expand-category-${category.id}`} - > -
-
- -
-
-

{category.name}

-

{category.description}

-
+ {Object.entries(govCategories).map(([cat, items]) => ( + +

+ {cat} + + {(items.reduce((s: number, i: any) => s + (i.currentScore || 0), 0) / items.length).toFixed(1)}/10 + +

+
+ {items.map((g: any) => ( +
+
+

{g.criterionName}

+

Impacto: {g.valuationImpactPct}% no valuation

+
+
+ {g.currentScore || 0} + updateGovScore(g.id, parseInt(e.target.value))} + className="w-32 accent-primary" + data-testid={`slider-gov-${g.id}`} + /> + /10 +
+
+ ))}
-
-
- {progress.completed}/{progress.total} -

itens

-
- -
-
+ + ))} + + )} + {governance.length === 0 && ( + + +

Nenhum critério de governança configurado

+

Inicialize para avaliar 20 critérios em 6 categorias

+
+ )} + - {isExpanded && ( -
- {categoryItems.map((item) => { - const itemProgress = getItemProgress(item.id); - const status = itemProgress?.status || "pending"; - + {/* ===== SWOT TAB ===== */} + +
+

Análise SWOT

+ +
+ +
+ {(["strengths", "weaknesses", "opportunities", "threats"] as const).map((q) => { + const labels = { strengths: "Forças", weaknesses: "Fraquezas", opportunities: "Oportunidades", threats: "Ameaças" }; + const colors = { strengths: "bg-green-50 border-green-200 dark:bg-green-950/20", weaknesses: "bg-red-50 border-red-200 dark:bg-red-950/20", opportunities: "bg-blue-50 border-blue-200 dark:bg-blue-950/20", threats: "bg-orange-50 border-orange-200 dark:bg-orange-950/20" }; + const icons = { strengths: , weaknesses: , opportunities: , threats: }; + const items = swotItems.filter((s: any) => s.quadrant === q); + return ( + +

{icons[q]} {labels[q]}

+
+ {items.map((item: any) => ( +
+
+

{item.item}

+
+ Impacto: {item.impact} + Val: {item.valuationRelevance}/10 +
+
+ +
+ ))} + {items.length === 0 &&

Nenhum item

} +
+
+ ); + })} +
+ + +

Adicionar Item

+
+ + setSwotNewItem({ ...swotNewItem, item: e.target.value })} className="flex-1" data-testid="input-swot-item" /> + + +
+
+
+ + {/* ===== CANVAS TAB ===== */} + +

Canvas Dual (Atual vs Projetado)

+
+ {["key_partners", "key_activities", "value_proposition", "customer_relationships", "customer_segments", + "key_resources", "channels", "cost_structure", "revenue_streams"].map((blockType) => { + const labels: Record = { + key_partners: "Parceiros-Chave", key_activities: "Atividades-Chave", + key_resources: "Recursos-Chave", value_proposition: "Proposta de Valor", + customer_relationships: "Relacionamento", channels: "Canais", + customer_segments: "Segmentos", cost_structure: "Estrutura de Custos", + revenue_streams: "Fontes de Receita", + }; + const block = canvasBlocks.find((b: any) => b.blockType === blockType); + return ( + +
{labels[blockType]}
+
+ {block?.items?.length > 0 ? ( +
    + {(block.items as string[]).slice(0, 4).map((item: string, i: number) => ( +
  • {item}
  • + ))} +
+ ) : ( + Clique para editar + )} +
+ {block?.score !== undefined && ( +
+ Score: + {block.score}/10 +
+ )} +
+ ); + })} +
+
+ + {/* ===== PDCA TAB ===== */} + +

PDCA - Melhoria Contínua

+ + +

Mapa de Calor

+
+ + + + + {["critical", "high", "medium", "low"].map(p => ( + + ))} + + + + {pdcaHeatMap.areas.map(area => ( + + + {pdcaHeatMap.priorities.map(pri => { + const count = pdcaHeatMap.map[`${area}-${pri}`] || 0; + const bg = count === 0 ? "bg-gray-100 dark:bg-gray-800" : + count <= 1 ? "bg-green-200 dark:bg-green-800" : + count <= 2 ? "bg-yellow-200 dark:bg-yellow-800" : + "bg-red-200 dark:bg-red-800"; + return ( + + ); + })} + + ))} + +
Área{p}
{area} + + {count || ""} + +
+
+
+ +
+ {(["plan", "do", "check", "act"] as const).map(phase => { + const phaseLabels = { plan: "Planejar", do: "Executar", check: "Verificar", act: "Agir" }; + const phaseColors = { plan: "border-blue-300", do: "border-yellow-300", check: "border-green-300", act: "border-purple-300" }; + const items = pdcaItems.filter((p: any) => p.phase === phase); + return ( + +
{phaseLabels[phase]} ({items.length})
+
+ {items.map((item: any) => ( +
+
+ {item.title} +
+ {phase !== "act" && ( + + )} + +
+
+ {item.originArea} | {item.priority} +
+ ))} +
+
+ ); + })} +
+ + +

Nova Ação PDCA

+
+ setPdcaNewItem({ ...pdcaNewItem, title: e.target.value })} data-testid="input-pdca-title" /> + + + +
+
+
+ + {/* ===== ANALYSIS / BI TAB ===== */} + +

Dashboard de Análise

+ + {results.length === 0 ? ( + + +

Execute o cálculo de valuation para ver análises

+ +
+ ) : ( + <> +
+ +

Decomposição por Método (Base)

+ + + `${name}: R$ ${value.toFixed(1)}M`}> + {methodPieData.map((_, i) => )} + + `R$ ${v.toFixed(2)}M`} /> + + +
+ + +

Cenários

+ + + + + + `R$ ${v.toFixed(2)}M`} /> + + + +
+ + +

Resultados por Método

+
+ {baseResults.map((r: any, i: number) => { + const methodLabel: Record = { dcf: "DCF", ev_ebitda: "EV/EBITDA", ev_revenue: "EV/Receita", patrimonial: "Patrimonial", assets: "Ativos" }; return ( -
-
-
- -
-
- {item.code} -
- {item.title} -
- {item.isRequired ? ( - Obrigatório - ) : null} -
- {item.description && ( -

{item.description}

- )} - {item.format && ( -

Formato: {item.format}

- )} - - {/* Attachments Section */} - {itemAttachments[item.id]?.length > 0 && ( -
-

Anexos:

-
- {itemAttachments[item.id].map((att: any) => ( -
- - - {att.originalName} - - -
- ))} -
-
- )} - - {itemProgress?.agentAnalysis && ( - -
- -
-

Análise do Agent

-

{itemProgress.agentAnalysis}

-
-
-
- )} -
-
-
- - - - - - - - {item.code} - {item.title} - Solicite assistência do Agent para este item - -
-
- -