Files
fspm-web/src/components/admin/pedido/CambiarEstadoPedido.tsx
T
Uriel JarethandClaude Opus 5 01b817d098 feat: sitio comercial FSPM con micro CRM interno
Sitio público orientado a venta consultiva e implementación de las
recomendaciones de la auditoría de marca y mercado:

- Inicio con tres rutas (industria, riesgo, cumplimiento) y clasificador
  de riesgo en el héroe, contra el hallazgo de que el visitante tenía que
  inferir qué solución le correspondía.
- Cinco páginas por segmento prioritario y catálogo comparable por clase
  de fuego, capacidad y agente.
- Los claims sin evidencia por modelo quedan aislados y se presentan como
  compromiso de documentación, nunca como característica del producto.
- SEO técnico completo: metadatos por página, sitemap derivado de los
  datos, JSON-LD, iconos y tarjeta social generadas.

Micro CRM:

- Captura por formulario embebido y por widget de WhatsApp que registra
  antes de abrir la conversación.
- Atribución de primer toque que sobrevive a la navegación.
- Deduplicación de contacto; un mismo contacto acumula oportunidades.
- Motor de asignación por reglas con reparto por turno de respaldo.
- Panel con dos roles de alcance distinto, tablero analítico, bitácora,
  pedidos y reglas. No enlazado desde el sitio público y con noindex.

Empaquetado en Docker multietapa con la base de demostración horneada en
la imagen y el volumen de datos separado, para que los leads capturados
sobrevivan a los redespliegues.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 00:07:20 -06:00

96 lines
2.8 KiB
TypeScript

"use client";
import { useState } from "react";
import {
ESTADOS_PEDIDO,
ESTADO_PEDIDO_ETIQUETA,
type EstadoPedido,
} from "@/lib/crm/constantes";
import { accionCambiarEstadoPedido } from "@/lib/crm/acciones";
import { AvisoAccion, usarAccion } from "@/components/admin/usarAccion";
/**
* Cambio de estado del pedido. Cancelar pide confirmación explícita: es el
* único estado del que no se sale sin explicar por qué.
*/
export function CambiarEstadoPedido({
id,
estado,
}: {
id: string;
estado: EstadoPedido;
}) {
const { enviando, aviso, ejecutar } = usarAccion();
const [elegido, setElegido] = useState<EstadoPedido>(estado);
const confirmandoCancelacion = elegido === "CANCELADO" && estado !== "CANCELADO";
function alCambiar(v: string) {
const siguiente = v as EstadoPedido;
setElegido(siguiente);
if (siguiente === "CANCELADO") return;
if (siguiente === estado) return;
void ejecutar(
() => accionCambiarEstadoPedido(id, siguiente),
`Pedido en ${ESTADO_PEDIDO_ETIQUETA[siguiente].toLowerCase()}.`,
);
}
return (
<div className="grid gap-2">
<label htmlFor="pedido-estado" className="field-label">
Estado del pedido
</label>
<select
id="pedido-estado"
value={elegido}
disabled={enviando}
onChange={(e) => alCambiar(e.target.value)}
className="field w-full py-2 text-sm"
style={{ appearance: "none", WebkitAppearance: "none" }}
>
{ESTADOS_PEDIDO.map((e) => (
<option key={e} value={e}>
{ESTADO_PEDIDO_ETIQUETA[e]}
</option>
))}
</select>
{confirmandoCancelacion ? (
<div className="grid gap-2 rounded-[3px] border border-signal-dim bg-ink px-3 py-3">
<p className="text-xs leading-relaxed text-muted">
Cancelar el pedido lo saca del flujo de surtido. Confírmalo si es lo que quieres.
</p>
<div className="flex flex-wrap gap-2">
<button
type="button"
disabled={enviando}
onClick={() =>
void ejecutar(
() => accionCambiarEstadoPedido(id, "CANCELADO"),
"Pedido cancelado.",
)
}
className="btn btn-signal btn-sm"
style={{ appearance: "none" }}
>
{enviando ? "Cancelando…" : "Cancelar pedido"}
</button>
<button
type="button"
disabled={enviando}
onClick={() => setElegido(estado)}
className="btn btn-ghost btn-sm"
style={{ appearance: "none" }}
>
No cancelar
</button>
</div>
</div>
) : null}
<AvisoAccion aviso={aviso} />
</div>
);
}