Propuesta IA: cerrar los hallazgos medios de la auditoria
El mas urgente lo cause yo al hacer que el bloqueo bloqueara de verdad: si un aviso bloqueante caia en un campo que el panel no dejaba editar, el asesor quedaba sin salida salvo regenerar. Ocho campos eran editables y bastantes mas se imprimen al cliente. Ahora se pueden editar tambien la cita destacada y su autor, las dimensiones del valor y su nota de metodologia, los resultados con su metrica y periodo, los materiales, las decisiones con quien decide, y el backlog. El boton de descarga del documento del cliente aparece deshabilitado cuando hay bloqueantes, en vez de invitar a un clic que devuelve 409. Doble propuesta: se rechaza generar. El documento asume UNA lista de alcance y UNA caja de totales, asi que sumaria las dos opciones —que son alternativas excluyentes— y mostraria un total que no existe. Soportarlas es rediseniar el documento; mientras tanto es mejor no producir uno incorrecto. Hoy no hay cotizaciones dobles en produccion, asi que no bloquea a nadie. La transcripcion ahora se guarda con la propuesta (columna nueva, migracion aditiva). Solo vivia en memoria durante la generacion, asi que al editar y revalidar, R3 se quedaba sin fuente contra la cual comprobar que la cita destacada siguiera siendo literal, y el aviso desaparecia solo. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
434a427f70
commit
0c127b44c4
@@ -172,6 +172,7 @@ export default function PropuestaIAPanel({ cotizacionId }: { cotizacionId: strin
|
||||
|
||||
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 (
|
||||
@@ -371,6 +372,167 @@ export default function PropuestaIAPanel({ cotizacionId }: { cotizacionId: strin
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Los grupos de abajo tambien se imprimen al cliente, asi que tambien
|
||||
pueden disparar un aviso bloqueante. Si no fueran editables, un
|
||||
bloqueante ahi no tendria mas salida que regenerar la propuesta. */}
|
||||
|
||||
{citaDestacada && (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-wide text-muted mb-1">
|
||||
Cita destacada
|
||||
</label>
|
||||
<p className="text-xs text-muted mb-2">
|
||||
Se imprime literal. Editala solo para quitar un dato sensible, nunca para
|
||||
mejorarle la redaccion al cliente.
|
||||
</p>
|
||||
<textarea
|
||||
value={citaDestacada.textoLiteral}
|
||||
onChange={(e) =>
|
||||
editar((p) => {
|
||||
const c = p.hechos.citas.find((x) => x.id === p.redaccion.citaDestacadaId);
|
||||
if (c) c.textoLiteral = e.target.value;
|
||||
})
|
||||
}
|
||||
rows={2}
|
||||
className={`${INPUT} mb-2`}
|
||||
/>
|
||||
<input
|
||||
value={citaDestacada.quienLoDijo}
|
||||
onChange={(e) =>
|
||||
editar((p) => {
|
||||
const c = p.hechos.citas.find((x) => x.id === p.redaccion.citaDestacadaId);
|
||||
if (c) c.quienLoDijo = e.target.value;
|
||||
})
|
||||
}
|
||||
placeholder="Quien lo dijo (usa el rol, no el nombre)"
|
||||
className={INPUT}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{propuesta.diagnostico.valorProblema.dimensiones.length > 0 && (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-wide text-muted mb-2">
|
||||
Lo que cuesta no resolverlo
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{propuesta.diagnostico.valorProblema.dimensiones.map((dim, i) => (
|
||||
<div key={i} className="flex gap-2 items-start">
|
||||
<span className="text-xs text-muted mt-2.5 shrink-0 w-20">{dim.tipo}</span>
|
||||
<textarea
|
||||
value={dim.descripcion}
|
||||
onChange={(e) => editar((p) => { p.diagnostico.valorProblema.dimensiones[i].descripcion = e.target.value; })}
|
||||
rows={2}
|
||||
className={INPUT}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
value={propuesta.diagnostico.valorProblema.notaMetodologia}
|
||||
onChange={(e) => editar((p) => { p.diagnostico.valorProblema.notaMetodologia = e.target.value; })}
|
||||
rows={2}
|
||||
placeholder="Nota de metodologia"
|
||||
className={`${INPUT} mt-2`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{propuesta.diagnostico.resultados.length > 0 && (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-wide text-muted mb-2">
|
||||
Resultados a lograr
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{propuesta.diagnostico.resultados.map((r, i) => (
|
||||
<div key={i} className="space-y-1">
|
||||
<textarea
|
||||
value={r.enunciado}
|
||||
onChange={(e) => editar((p) => { p.diagnostico.resultados[i].enunciado = e.target.value; })}
|
||||
rows={2}
|
||||
className={INPUT}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
value={r.metrica}
|
||||
onChange={(e) => editar((p) => { p.diagnostico.resultados[i].metrica = e.target.value; })}
|
||||
placeholder="Metrica"
|
||||
className={INPUT}
|
||||
/>
|
||||
<input
|
||||
value={r.periodoMedicion}
|
||||
onChange={(e) => editar((p) => { p.diagnostico.resultados[i].periodoMedicion = e.target.value; })}
|
||||
placeholder="Periodo"
|
||||
className={INPUT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(propuesta.hechos.materialesPendientes.length > 0 ||
|
||||
propuesta.hechos.decisionesPendientes.length > 0) && (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-wide text-muted mb-2">
|
||||
Que necesitamos de ustedes
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{propuesta.hechos.materialesPendientes.map((m, i) => (
|
||||
<input
|
||||
key={`mat-${i}`}
|
||||
value={m.texto}
|
||||
onChange={(e) => editar((p) => { p.hechos.materialesPendientes[i].texto = e.target.value; })}
|
||||
className={INPUT}
|
||||
/>
|
||||
))}
|
||||
{propuesta.hechos.decisionesPendientes.map((dd, i) => (
|
||||
<div key={`dec-${i}`} className="flex gap-2">
|
||||
<textarea
|
||||
value={dd.texto}
|
||||
onChange={(e) => editar((p) => { p.hechos.decisionesPendientes[i].texto = e.target.value; })}
|
||||
rows={1}
|
||||
className={INPUT}
|
||||
/>
|
||||
<input
|
||||
value={dd.quienDecide}
|
||||
onChange={(e) => editar((p) => { p.hechos.decisionesPendientes[i].quienDecide = e.target.value; })}
|
||||
placeholder="Quien decide"
|
||||
className={`${INPUT} max-w-[11rem] shrink-0`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{propuesta.redaccion.backlogEvolucion.length > 0 && (
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-wide text-muted mb-2">
|
||||
Registrado para mas adelante
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{propuesta.redaccion.backlogEvolucion.map((b, i) => (
|
||||
<div key={i} className="flex gap-2">
|
||||
<textarea
|
||||
value={b.problema}
|
||||
onChange={(e) => editar((p) => { p.redaccion.backlogEvolucion[i].problema = e.target.value; })}
|
||||
rows={1}
|
||||
className={INPUT}
|
||||
/>
|
||||
<input
|
||||
value={b.momentoSugerido}
|
||||
onChange={(e) => editar((p) => { p.redaccion.backlogEvolucion[i].momentoSugerido = e.target.value; })}
|
||||
placeholder="Cuando"
|
||||
className={`${INPUT} max-w-[11rem] shrink-0`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2 border-t border-border">
|
||||
@@ -383,13 +545,26 @@ export default function PropuestaIAPanel({ cotizacionId }: { cotizacionId: strin
|
||||
{guardando ? "Guardando..." : sucio ? "Guardar cambios" : "Sin cambios"}
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={`/api/propuesta-ia/${cotizacionId}/pdf`}
|
||||
className="flex items-center gap-2 px-4 py-2 border border-border rounded-lg text-sm hover:bg-gray-50"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Propuesta consultiva (PDF)
|
||||
</a>
|
||||
{/* El servidor devuelve 409 si hay bloqueantes; aqui se refleja para no
|
||||
invitar a un clic que va a fallar. La descarga del anexo interno
|
||||
sigue disponible: es la que ayuda a entender que corregir. */}
|
||||
{bloqueantes.length > 0 ? (
|
||||
<span
|
||||
className="flex items-center gap-2 px-4 py-2 border border-border rounded-lg text-sm text-muted bg-gray-50 cursor-not-allowed"
|
||||
title="Resuelve los avisos bloqueantes y guarda para habilitar la descarga"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Propuesta consultiva (PDF) — bloqueada
|
||||
</span>
|
||||
) : (
|
||||
<a
|
||||
href={`/api/propuesta-ia/${cotizacionId}/pdf`}
|
||||
className="flex items-center gap-2 px-4 py-2 border border-border rounded-lg text-sm hover:bg-gray-50"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Propuesta consultiva (PDF)
|
||||
</a>
|
||||
)}
|
||||
|
||||
<a
|
||||
href={`/api/propuesta-ia/${cotizacionId}/pdf?anexo=1`}
|
||||
|
||||
Reference in New Issue
Block a user