"use client"; import { useCallback, useEffect, useState } from "react"; import { Sparkles, Download, Save, AlertTriangle, Info, Lock, RefreshCw, ChevronDown, ChevronRight } from "lucide-react"; import { useToast, useConfirm } from "@/components/ui/DialogProvider"; // Panel de la propuesta consultiva: generar, revisar, editar y descargar. // // Regla de diseno deliberada: los avisos van ARRIBA de los botones de descarga. // El objetivo es que revisar sea mas facil que aprobar, no al reves. type Confianza = "confirmado" | "estimado" | "por_validar"; type Urgencia = "rojo" | "ambar" | "azul" | "verde"; interface Aviso { regla: string; severidad: "bloqueante" | "advertencia"; mensaje: string; ruta?: string; } interface Propuesta { hechos: { citas: { id: string; textoLiteral: string; quienLoDijo: string }[]; hechos: { id: string; enunciado: string; confianza: Confianza; citas: string[] }[]; materialesPendientes: { texto: string; bloqueaEntrega: boolean }[]; decisionesPendientes: { texto: string; quienDecide: string }[]; mencionesFueraDeAlcance: string[]; redFlags: { senal: string; severidad: string }[]; }; diagnostico: { hallazgos: { id: string; urgencia: Urgencia; titulo: string; descripcion: string; confianza: Confianza; citas: string[]; hechos: string[] }[]; valorProblema: { dimensiones: { tipo: string; descripcion: string; calculo: { factores: { nombre: string; valor: number }[]; montoAnualMXN: number | null }; confianza: Confianza; hechos: string[] }[]; notaMetodologia: string; }; resultados: { enunciado: string; metrica: string; lineaBase: string | null; periodoMedicion: string }[]; }; redaccion: { hero: { titulo: string; subtitulo: string }; citaDestacadaId: string; alcance: { refPartida: string; descripcionResultado: string }[]; beneficios: { etiqueta: string; texto: string; hallazgoId: string }[]; exclusiones: { texto: string; razon: string }[]; backlogEvolucion: { problema: string; momentoSugerido: string }[]; notasInternas: string[]; }; } const COLOR_URGENCIA: Record = { rojo: "bg-red-500", ambar: "bg-amber-500", azul: "bg-blue-500", verde: "bg-green-500", }; const ETIQUETA_URGENCIA: Record = { rojo: "Problema critico", ambar: "Area de mejora", azul: "Oportunidad", verde: "Ventaja existente", }; const INPUT = "w-full px-3 py-2 border border-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary"; export default function PropuestaIAPanel({ cotizacionId }: { cotizacionId: string; numero?: string }) { const toast = useToast(); const confirm = useConfirm(); const [abierto, setAbierto] = useState(false); const [cargando, setCargando] = useState(true); const [generando, setGenerando] = useState(false); const [guardando, setGuardando] = useState(false); const [transcripcion, setTranscripcion] = useState(""); const [propuesta, setPropuesta] = useState(null); const [avisos, setAvisos] = useState([]); const [editada, setEditada] = useState(false); const [sucio, setSucio] = useState(false); const cargar = useCallback(async () => { try { const r = await fetch(`/api/propuesta-ia/${cotizacionId}`); const d = await r.json(); if (d.propuesta) { setPropuesta(d.propuesta); setAvisos(Array.isArray(d.avisos) ? d.avisos : []); setEditada(Boolean(d.editada)); setAbierto(true); } } finally { setCargando(false); } }, [cotizacionId]); useEffect(() => { void cargar(); }, [cargar]); async function generar() { if (propuesta) { const ok = await confirm({ title: "Regenerar la propuesta", message: "Se generara una propuesta nueva. La version actual y tus ediciones quedaran reemplazadas.", confirmText: "Regenerar", danger: true, }); if (!ok) return; } setGenerando(true); try { const r = await fetch(`/api/propuesta-ia/${cotizacionId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ transcripcion }), }); const d = await r.json(); if (!r.ok) { toast(d.error || "No se pudo generar la propuesta", "error"); return; } setPropuesta(d.propuesta); setAvisos(Array.isArray(d.avisos) ? d.avisos : []); setEditada(false); setSucio(false); setAbierto(true); const bloqueantes = (d.avisos as Aviso[]).filter((a) => a.severidad === "bloqueante").length; toast( bloqueantes > 0 ? `Propuesta generada con ${bloqueantes} aviso(s) que debes resolver antes de enviarla.` : "Propuesta generada. Revisala antes de enviarla.", bloqueantes > 0 ? "error" : "success" ); } catch (e) { toast(e instanceof Error ? e.message : "Error al generar", "error"); } finally { setGenerando(false); } } async function guardar() { if (!propuesta) return; setGuardando(true); try { const r = await fetch(`/api/propuesta-ia/${cotizacionId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ propuesta }), }); const d = await r.json(); if (!r.ok) { toast(typeof d.error === "string" ? d.error : "No se pudo guardar", "error"); return; } setAvisos(Array.isArray(d.avisos) ? d.avisos : []); setEditada(true); setSucio(false); toast("Cambios guardados y revalidados.", "success"); } finally { setGuardando(false); } } /** Aplica un cambio al objeto y marca el panel como sucio. */ function editar(fn: (p: Propuesta) => void) { setPropuesta((prev) => { if (!prev) return prev; const copia = structuredClone(prev) as Propuesta; fn(copia); return copia; }); setSucio(true); } const bloqueantes = avisos.filter((a) => a.severidad === "bloqueante"); const advertencias = avisos.filter((a) => a.severidad === "advertencia"); const citaDestacada = propuesta?.hechos.citas.find((c) => c.id === propuesta.redaccion.citaDestacadaId); if (cargando) { return (

Cargando propuesta consultiva...

); } return (
{abierto && (
{/* ── Entrada ── */}

Pega aqui lo que se dijo en la reunion de discovery. Sin transcripcion, la IA trabaja solo con las observaciones y notas de la cotizacion, y marcara mas cosas por validar.