Files
ESPDocs/app/diagnostico/page.jsx
T

821 lines
34 KiB
React

"use client";
import { useState, useMemo, useRef } from "react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import Link from "next/link";
import {
categoriasDiagnostico,
sintomasRapidos,
errosCatalogo,
regrasDecodificadorLog,
} from "@/lib/diagnosticoData";
import {
AlertTriangle,
Zap,
Cpu,
Layers,
Activity,
Terminal,
Wifi,
Search,
CheckCircle2,
XCircle,
Copy,
Check,
Sparkles,
HelpCircle,
ArrowRight,
ExternalLink,
ShieldAlert,
Wrench,
Code2,
ChevronDown,
ChevronUp,
X,
Filter,
RotateCcw,
Tag,
} from "lucide-react";
export default function DiagnosticoPage() {
const [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
const [sintomaAtivo, setSintomaAtivo] = useState(null);
const [cardAberto, setCardAberto] = useState(null);
const [linguagemCodigo, setLinguagemCodigo] = useState({}); // { [id]: 'arduino' | 'espidf' }
const [copiadoId, setCopiadoId] = useState(null);
const searchInputRef = useRef(null);
// Estado do Analisador de Logs
const [logInput, setLogInput] = useState("");
const [resultadoLog, setResultadoLog] = useState(null);
// Mapeamento de ícones e emojis por categoria
const iconMap = {
Zap: <Zap className="w-4 h-4" />,
Cpu: <Cpu className="w-4 h-4" />,
Layers: <Layers className="w-4 h-4" />,
Activity: <Activity className="w-4 h-4" />,
Terminal: <Terminal className="w-4 h-4" />,
Wifi: <Wifi className="w-4 h-4" />,
};
const emojiMap = {
alimentacao: "⚡",
"cpu-panics": "💥",
freertos: "🐕",
memoria: "💾",
"boot-flash": "🔌",
conectividade: "📡",
};
// Função de Análise do Log
const analisarLog = (texto) => {
setLogInput(texto);
if (!texto || texto.trim() === "") {
setResultadoLog(null);
return;
}
for (const regra of regrasDecodificadorLog) {
if (regra.padrao.test(texto)) {
setResultadoLog(regra);
return;
}
}
setResultadoLog({
titulo: "Padrão de erro não categorizado automaticamente",
tipo: "Desconhecido",
explicacao:
"Não identificamos uma assinatura clássica de Guru Meditation ou Brownout neste trecho. Verifique se a taxa de baud rate (ex: 115200) está correta e se o cabeçalho 'rst:' ou 'panic' está presente.",
acaoRecomendada:
"Procure por palavras-chave como 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' ou 'panic' no console serial.",
});
};
const copiarParaClipboard = (texto, id) => {
navigator.clipboard.writeText(texto);
setCopiadoId(id);
setTimeout(() => setCopiadoId(null), 2000);
};
// Filtragem dos erros
const errosFiltrados = useMemo(() => {
const termo = termoBusca.toLowerCase().trim();
return errosCatalogo.filter((erro) => {
const matchCategoria =
categoriaAtiva === "todos" || erro.categoria === categoriaAtiva;
const matchSintoma =
!sintomaAtivo || erro.sintomas.includes(sintomaAtivo);
const matchBusca =
!termo ||
erro.titulo.toLowerCase().includes(termo) ||
erro.resumo.toLowerCase().includes(termo) ||
erro.causaRaiz.toLowerCase().includes(termo) ||
erro.logSerial.toLowerCase().includes(termo);
return matchCategoria && matchSintoma && matchBusca;
});
}, [categoriaAtiva, sintomaAtivo, termoBusca]);
const temFiltroAtivo =
termoBusca.trim() !== "" ||
categoriaAtiva !== "todos" ||
sintomaAtivo !== null;
const limparTodosFiltros = () => {
setTermoBusca("");
setCategoriaAtiva("todos");
setSintomaAtivo(null);
searchInputRef.current?.focus();
};
const toggleCard = (id) => {
setCardAberto((prev) => (prev === id ? null : id));
};
const setLang = (id, lang) => {
setLinguagemCodigo((prev) => ({ ...prev, [id]: lang }));
};
const catAtivaObj = categoriasDiagnostico.find((c) => c.id === categoriaAtiva);
const sintomaAtivoObj = sintomasRapidos.find((s) => s.id === sintomaAtivo);
return (
<div className="bg-gradient-to-br from-slate-100 via-slate-50 to-amber-100/30 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main id="conteudo" className="px-4 sm:px-6 pt-12 pb-24 max-w-7xl mx-auto">
{/* Hero Section */}
<section className="text-center mb-10 select-none">
<div className="inline-flex items-center gap-2 bg-white dark:bg-slate-900/60 backdrop-blur-md px-4 py-2 rounded-full mb-5 border border-slate-250 dark:border-slate-800/80 shadow-xs">
<AlertTriangle className="w-4 h-4 text-amber-500" />
<span className="text-xs font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider">
Debugging & Engenharia de Firmware
</span>
</div>
<h1 className="text-3xl sm:text-5xl md:text-6xl font-display font-extrabold bg-gradient-to-r from-amber-600 via-red-600 to-purple-600 dark:from-amber-400 dark:via-red-400 dark:to-purple-400 bg-clip-text text-transparent mb-4 leading-tight tracking-tight">
Diagnóstico & Erros Comuns
</h1>
<p className="text-sm sm:text-base text-slate-600 dark:text-slate-400 max-w-3xl mx-auto leading-relaxed font-normal">
Guia técnico definitivo para diagnosticar <strong>Brownout</strong>,{" "}
<strong>Guru Meditation</strong>, <strong>Watchdog Timeouts</strong>,{" "}
<strong>Stack Overflow</strong> e falhas de hardware no ESP32. Entenda a causa raiz no silício e aplique soluções profissionais.
</p>
<div className="mt-3 text-xs text-slate-500 dark:text-slate-400">
Baseado no artigo publicado no{" "}
<a
href="https://cienciaembarcada.com.br/publicacoes/esp32-5-erros-comuns-e-como-resolve-los-profissionalmente/"
target="_blank"
rel="noopener noreferrer"
className="text-amber-600 dark:text-amber-400 underline font-semibold hover:opacity-80 inline-flex items-center gap-1"
>
Ciência Embarcada
<ExternalLink className="w-3 h-3" />
</a>
, ampliado com novos cenários e decodificador interativo.
</div>
</section>
{/* 1. DECODIFICADOR RÁPIDO DE LOG SERIAL */}
<section className="mb-8 bg-white/90 dark:bg-slate-900/90 backdrop-blur-xl rounded-3xl border border-slate-200 dark:border-slate-800 p-5 sm:p-7 shadow-sm">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-5">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-2xl bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20">
<Sparkles className="w-5 h-5" />
</div>
<div>
<h2 className="text-lg sm:text-xl font-bold text-slate-900 dark:text-slate-100">
Decodificador Rápido de Log Serial
</h2>
<p className="text-xs text-slate-500 dark:text-slate-400">
Cole o trecho do monitor serial para diagnosticar a falha instantaneamente.
</p>
</div>
</div>
{/* Exemplos Rápidos para Teste */}
<div className="flex flex-wrap items-center gap-1.5 text-xs">
<span className="text-slate-500 dark:text-slate-400 font-bold mr-1 text-[11px] uppercase tracking-wider">
Exemplos:
</span>
<button
type="button"
onClick={() =>
analisarLog(
"Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-amber-100 dark:hover:bg-amber-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
⚡ Brownout
</button>
<button
type="button"
onClick={() =>
analisarLog(
"Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
💥 LoadProhibited
</button>
<button
type="button"
onClick={() =>
analisarLog(
"Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-purple-100 dark:hover:bg-purple-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
🐕 Task Watchdog
</button>
<button
type="button"
onClick={() =>
analisarLog(
"mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-cyan-100 dark:hover:bg-cyan-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
🔒 Handshake TLS
</button>
</div>
</div>
<div className="relative">
<textarea
rows={3}
value={logInput}
onChange={(e) => analisarLog(e.target.value)}
placeholder="Cole aqui o log do monitor serial (ex: Guru Meditation Error, Brownout detector was triggered, Task watchdog, rst:0x1...)"
className="w-full bg-slate-950 text-emerald-400 font-mono text-xs sm:text-sm p-4 rounded-2xl border border-slate-800 focus:outline-none focus:ring-2 focus:ring-amber-500 placeholder:text-slate-600 resize-y shadow-inner"
/>
{logInput && (
<button
type="button"
onClick={() => analisarLog("")}
className="absolute top-3.5 right-3.5 text-xs text-slate-400 hover:text-slate-200 bg-slate-800 px-2.5 py-1 rounded-lg border border-slate-700 font-medium cursor-pointer"
>
Limpar
</button>
)}
</div>
{/* Card de Resultado da Análise */}
{resultadoLog && (
<div className="mt-4 p-4 sm:p-5 rounded-2xl bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900/50 transition-all">
<div className="flex items-start gap-3.5">
<div className="p-2 rounded-xl bg-amber-500 text-white shrink-0 mt-0.5 shadow-xs">
<ShieldAlert className="w-5 h-5" />
</div>
<div className="space-y-2 flex-1">
<div className="flex flex-wrap items-center gap-2">
<h3 className="font-bold text-slate-900 dark:text-slate-100 text-sm sm:text-base">
{resultadoLog.titulo}
</h3>
<span className="text-xs px-2.5 py-0.5 rounded-full font-bold bg-amber-200 dark:bg-amber-900 text-amber-800 dark:text-amber-200">
{resultadoLog.tipo}
</span>
</div>
<p className="text-xs sm:text-sm text-slate-700 dark:text-slate-300 leading-relaxed">
<strong>Causa provável:</strong> {resultadoLog.explicacao}
</p>
<p className="text-xs sm:text-sm text-amber-900 dark:text-amber-300 font-medium">
<strong>Ação recomendada:</strong> {resultadoLog.acaoRecomendada}
</p>
</div>
</div>
</div>
)}
</section>
{/* 2. PAINEL UNIFICADO DE BUSCA & FILTRAGEM (COMMAND DECK) */}
<section className="mb-10 bg-white/90 dark:bg-slate-900/90 backdrop-blur-xl rounded-3xl border border-slate-200 dark:border-slate-800 p-5 sm:p-7 shadow-sm transition-all">
{/* Barra de Pesquisa Principal */}
<div className="relative mb-5">
<div className="absolute left-4 top-1/2 -translate-y-1/2 p-1.5 rounded-xl bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400 pointer-events-none">
<Search className="w-4 h-4 sm:w-5 sm:h-5" />
</div>
<input
ref={searchInputRef}
type="text"
value={termoBusca}
onChange={(e) => setTermoBusca(e.target.value)}
placeholder="Buscar por erro, sintoma, registrador ou mensagem serial (ex: LoadProhibited, Brownout, IRAM, Stack Canary)..."
className="w-full bg-slate-50 dark:bg-slate-950/70 pl-13 sm:pl-14 pr-24 sm:pr-28 py-3.5 sm:py-4 rounded-2xl border border-slate-250 dark:border-slate-800 text-sm sm:text-base font-medium placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all shadow-inner"
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2">
{termoBusca ? (
<button
type="button"
onClick={() => {
setTermoBusca("");
searchInputRef.current?.focus();
}}
className="text-xs px-2.5 py-1 rounded-xl bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-700 font-semibold flex items-center gap-1 transition-colors cursor-pointer"
>
<X className="w-3.5 h-3.5" />
<span>Limpar</span>
</button>
) : (
<span className="hidden sm:inline-flex text-[11px] font-bold text-slate-500 dark:text-slate-400 bg-slate-200/60 dark:bg-slate-800/80 px-2.5 py-1 rounded-lg border border-slate-250 dark:border-slate-700">
{errosFiltrados.length} {errosFiltrados.length === 1 ? "falha" : "falhas"}
</span>
)}
</div>
</div>
{/* Atalhos Rápidos por Sintoma */}
<div className="flex items-center gap-2 mb-6 overflow-x-auto pb-1.5 scrollbar-none text-xs">
<span className="text-slate-500 dark:text-slate-400 font-bold shrink-0 flex items-center gap-1 text-[11px] uppercase tracking-wider">
<Tag className="w-3 h-3" />
Sintomas:
</span>
{sintomasRapidos.map((sintoma) => {
const ativo = sintomaAtivo === sintoma.id;
return (
<button
key={sintoma.id}
type="button"
onClick={() => setSintomaAtivo(ativo ? null : sintoma.id)}
className={`px-2.5 py-1 rounded-lg font-medium transition-all shrink-0 cursor-pointer border ${
ativo
? "bg-amber-600 text-white border-amber-600 shadow-xs font-bold"
: "bg-slate-100/80 dark:bg-slate-800/60 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-750 hover:bg-amber-50 dark:hover:bg-amber-950/40 hover:border-amber-300 dark:hover:border-amber-800"
}`}
>
<span>{sintoma.rotulo}</span>
</button>
);
})}
</div>
{/* Seletor de Categorias em Grid Tátil */}
<div className="space-y-2">
<div className="flex items-center justify-between text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">
<span className="flex items-center gap-1.5">
<Filter className="w-3.5 h-3.5 text-amber-500" />
Filtrar por Domínio Técnico
</span>
{categoriaAtiva !== "todos" && (
<button
type="button"
onClick={() => setCategoriaAtiva("todos")}
className="text-amber-600 dark:text-amber-400 hover:underline cursor-pointer lowercase first-letter:uppercase text-[11px]"
>
Ver todas ({errosCatalogo.length})
</button>
)}
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-7 gap-2">
{/* Botão Todas as Categorias */}
<button
type="button"
onClick={() => setCategoriaAtiva("todos")}
className={`p-2.5 rounded-xl text-xs font-bold transition-all text-left flex flex-col justify-between gap-1 cursor-pointer border ${
categoriaAtiva === "todos"
? "bg-amber-600 text-white border-amber-600 shadow-sm ring-2 ring-amber-500/20"
: "bg-slate-50 dark:bg-slate-950/60 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-800 hover:border-amber-300 dark:hover:border-amber-700"
}`}
>
<span className="text-base">✨</span>
<div className="flex items-center justify-between w-full">
<span className="truncate">Todas</span>
<span
className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
categoriaAtiva === "todos"
? "bg-white/20 text-white"
: "bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400"
}`}
>
{errosCatalogo.length}
</span>
</div>
</button>
{/* Categorias Individuais */}
{categoriasDiagnostico.map((cat) => {
const totalNaCat = errosCatalogo.filter(
(e) => e.categoria === cat.id
).length;
const ativa = categoriaAtiva === cat.id;
return (
<button
key={cat.id}
type="button"
onClick={() => setCategoriaAtiva(cat.id)}
className={`p-2.5 rounded-xl text-xs font-bold transition-all text-left flex flex-col justify-between gap-1 cursor-pointer border ${
ativa
? "bg-amber-600 text-white border-amber-600 shadow-sm ring-2 ring-amber-500/20"
: "bg-slate-50 dark:bg-slate-950/60 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-800 hover:border-amber-300 dark:hover:border-amber-700"
}`}
>
<span className="text-base">{emojiMap[cat.id] || "⚠️"}</span>
<div className="flex items-center justify-between w-full">
<span className="truncate">{cat.nome.split(" ")[0]}</span>
<span
className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
ativa
? "bg-white/20 text-white"
: "bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400"
}`}
>
{totalNaCat}
</span>
</div>
</button>
);
})}
</div>
</div>
{/* Fita de Filtros Ativos */}
{temFiltroAtivo && (
<div className="mt-4 pt-3 border-t border-slate-150 dark:border-slate-800/80 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-1.5 text-xs">
<span className="text-slate-500 dark:text-slate-400 font-semibold mr-1">
Filtros ativos:
</span>
{termoBusca && (
<span className="inline-flex items-center gap-1 bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-300 px-2.5 py-0.5 rounded-full border border-amber-200 dark:border-amber-800 font-medium">
Busca: <strong>"{termoBusca}"</strong>
<button
type="button"
onClick={() => setTermoBusca("")}
className="hover:text-amber-950 dark:hover:text-amber-100"
>
<X className="w-3 h-3" />
</button>
</span>
)}
{categoriaAtiva !== "todos" && (
<span className="inline-flex items-center gap-1 bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-300 px-2.5 py-0.5 rounded-full border border-amber-200 dark:border-amber-800 font-medium">
Categoria: <strong>{catAtivaObj?.nome}</strong>
<button
type="button"
onClick={() => setCategoriaAtiva("todos")}
className="hover:text-amber-950 dark:hover:text-amber-100"
>
<X className="w-3 h-3" />
</button>
</span>
)}
{sintomaAtivo && (
<span className="inline-flex items-center gap-1 bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-300 px-2.5 py-0.5 rounded-full border border-amber-200 dark:border-amber-800 font-medium">
Sintoma: <strong>{sintomaAtivoObj?.rotulo}</strong>
<button
type="button"
onClick={() => setSintomaAtivo(null)}
className="hover:text-amber-950 dark:hover:text-amber-100"
>
<X className="w-3 h-3" />
</button>
</span>
)}
</div>
<button
type="button"
onClick={limparTodosFiltros}
className="text-xs font-semibold text-red-600 dark:text-red-400 hover:underline flex items-center gap-1 cursor-pointer"
>
<RotateCcw className="w-3 h-3" />
<span>Redefinir filtros</span>
</button>
</div>
)}
</section>
{/* 3. LISTA DE ERROS CATALOGADOS */}
<section className="space-y-6">
{errosFiltrados.length === 0 ? (
<div className="text-center py-16 bg-white dark:bg-slate-900 rounded-3xl border border-slate-250 dark:border-slate-800 shadow-sm p-8">
<HelpCircle className="w-12 h-12 text-slate-400 mx-auto mb-3" />
<h3 className="text-lg font-bold text-slate-700 dark:text-slate-300">
Nenhuma falha encontrada com os filtros atuais
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 max-w-md mx-auto">
Tente buscar por termos mais genéricos ou limpe os filtros de sintoma e categoria.
</p>
<button
type="button"
onClick={limparTodosFiltros}
className="mt-4 px-4 py-2 bg-amber-600 text-white rounded-xl text-xs font-bold hover:bg-amber-700 cursor-pointer shadow-xs inline-flex items-center gap-1.5"
>
<RotateCcw className="w-3.5 h-3.5" />
<span>Limpar todos os filtros</span>
</button>
</div>
) : (
errosFiltrados.map((erro) => {
const estaAberto = cardAberto === erro.id;
const lang = linguagemCodigo[erro.id] || "arduino";
const codigoAtual =
lang === "arduino" ? erro.codigoArduino : erro.codigoEspIdf;
const catInfo = categoriasDiagnostico.find(
(c) => c.id === erro.categoria
);
return (
<article
key={erro.id}
id={erro.id}
className={`bg-white dark:bg-slate-900/90 rounded-3xl border transition-all duration-200 overflow-hidden shadow-xs ${
estaAberto
? "border-amber-400/80 dark:border-amber-500/50 ring-2 ring-amber-400/20"
: "border-slate-250 dark:border-slate-800 hover:border-slate-350 dark:hover:border-slate-700"
}`}
>
{/* Cabeçalho do Card */}
<div
onClick={() => toggleCard(erro.id)}
className="p-5 sm:p-6 cursor-pointer flex flex-col md:flex-row md:items-center justify-between gap-4 select-none"
>
<div className="space-y-2 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className="inline-flex items-center gap-1 text-xs px-2.5 py-0.5 rounded-full font-semibold bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border border-slate-250 dark:border-slate-700">
<span>{emojiMap[erro.categoria] || "⚠️"}</span>
<span>{catInfo?.nome}</span>
</span>
<span
className={`text-xs px-2.5 py-0.5 rounded-full font-semibold ${
erro.gravidade === "Crítica"
? "bg-red-100 dark:bg-red-950 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-900"
: erro.gravidade === "Alta"
? "bg-amber-100 dark:bg-amber-950 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-900"
: "bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-900"
}`}
>
Gravidade: {erro.gravidade}
</span>
</div>
<h3 className="text-lg sm:text-xl font-bold text-slate-900 dark:text-slate-100">
{erro.titulo}
</h3>
<p className="text-xs sm:text-sm text-slate-600 dark:text-slate-400">
{erro.resumo}
</p>
</div>
<div className="flex items-center gap-3 self-end md:self-center shrink-0">
<span className="text-xs font-semibold text-amber-600 dark:text-amber-400 flex items-center gap-1">
{estaAberto ? "Ocultar detalhes" : "Ver diagnóstico & solução"}
</span>
<div className="p-2 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300">
{estaAberto ? (
<ChevronUp className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)}
</div>
</div>
</div>
{/* Conteúdo Expandido */}
{estaAberto && (
<div className="px-5 sm:px-6 pb-6 pt-2 border-t border-slate-200 dark:border-slate-800/80 space-y-6">
{/* Snippet do Log Serial */}
<div>
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400 flex items-center gap-1.5">
<Terminal className="w-3.5 h-3.5" />
Assinatura no Monitor Serial (Log Típico)
</span>
<button
type="button"
onClick={() =>
copiarParaClipboard(erro.logSerial, `log-${erro.id}`)
}
className="text-xs text-slate-400 hover:text-slate-200 inline-flex items-center gap-1 cursor-pointer"
>
{copiadoId === `log-${erro.id}` ? (
<>
<Check className="w-3 h-3 text-emerald-400" />
<span className="text-emerald-400 font-semibold">Copiado!</span>
</>
) : (
<>
<Copy className="w-3 h-3" />
<span>Copiar log</span>
</>
)}
</button>
</div>
<pre className="bg-slate-950 text-emerald-400 font-mono text-xs sm:text-sm p-3.5 rounded-2xl overflow-x-auto border border-slate-800 leading-relaxed">
{erro.logSerial}
</pre>
</div>
{/* Causa Raiz Técnica */}
<div>
<h4 className="text-sm font-bold text-slate-900 dark:text-slate-100 mb-1.5 flex items-center gap-1.5">
<Cpu className="w-4 h-4 text-amber-500" />
Causa Raiz no Silício / RTOS / Hardware
</h4>
<p className="text-xs sm:text-sm text-slate-700 dark:text-slate-300 leading-relaxed bg-slate-50 dark:bg-slate-900/50 p-4 rounded-2xl border border-slate-200 dark:border-slate-800">
{erro.causaRaiz}
</p>
</div>
{/* Comparativo Didático: Gambiarra vs Solução de Engenharia */}
<div className="grid md:grid-cols-2 gap-4">
{/* Abordagem Incorreta (Gambiarra) */}
<div className="p-4 sm:p-5 rounded-2xl bg-red-50/70 dark:bg-red-950/20 border border-red-200 dark:border-red-900/40 space-y-2">
<div className="flex items-center gap-2 text-red-700 dark:text-red-400 font-bold text-sm">
<XCircle className="w-4 h-4 shrink-0" />
<span>Abordagem Amadora ("Gambiarra")</span>
</div>
<p className="text-xs font-semibold text-slate-800 dark:text-slate-200">
{erro.gambiarra.titulo}
</p>
<p className="text-xs text-slate-600 dark:text-slate-400">
{erro.gambiarra.descricao}
</p>
<div className="text-xs text-red-800 dark:text-red-300 bg-red-100/80 dark:bg-red-900/40 p-2.5 rounded-xl font-medium">
<strong>Consequência:</strong> {erro.gambiarra.consequencia}
</div>
</div>
{/* Solução de Engenharia (Boas Práticas) */}
<div className="p-4 sm:p-5 rounded-2xl bg-emerald-50/70 dark:bg-emerald-950/20 border border-emerald-200 dark:border-emerald-900/40 space-y-2">
<div className="flex items-center gap-2 text-emerald-700 dark:text-emerald-400 font-bold text-sm">
<CheckCircle2 className="w-4 h-4 shrink-0" />
<span>Solução Profissional (Engenharia)</span>
</div>
<p className="text-xs font-semibold text-slate-800 dark:text-slate-200">
{erro.solucaoEngenharia.titulo}
</p>
<ul className="space-y-1.5 pt-1">
{erro.solucaoEngenharia.passos.map((passo, idx) => (
<li
key={idx}
className="text-xs text-slate-700 dark:text-slate-300 flex items-start gap-1.5"
>
<span className="text-emerald-500 font-bold">✓</span>
<span>{passo}</span>
</li>
))}
</ul>
</div>
</div>
{/* Bloco de Código (Arduino IDE vs ESP-IDF) */}
<div className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-1 bg-slate-200 dark:bg-slate-800 p-1 rounded-xl">
<button
type="button"
onClick={() => setLang(erro.id, "arduino")}
className={`text-xs px-3 py-1 rounded-lg font-bold transition-all cursor-pointer ${
lang === "arduino"
? "bg-white dark:bg-slate-900 text-teal-600 dark:text-teal-400 shadow-xs"
: "text-slate-600 dark:text-slate-400 hover:text-slate-900"
}`}
>
Arduino C++
</button>
<button
type="button"
onClick={() => setLang(erro.id, "espidf")}
className={`text-xs px-3 py-1 rounded-lg font-bold transition-all cursor-pointer ${
lang === "espidf"
? "bg-white dark:bg-slate-900 text-red-600 dark:text-red-400 shadow-xs"
: "text-slate-600 dark:text-slate-400 hover:text-slate-900"
}`}
>
ESP-IDF (C)
</button>
</div>
<button
type="button"
onClick={() =>
copiarParaClipboard(codigoAtual, `code-${erro.id}`)
}
className="text-xs px-3 py-1.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 inline-flex items-center gap-1.5 border border-slate-250 dark:border-slate-700 cursor-pointer font-medium"
>
{copiadoId === `code-${erro.id}` ? (
<>
<Check className="w-3.5 h-3.5 text-emerald-500" />
<span className="text-emerald-500 font-semibold">Copiado!</span>
</>
) : (
<>
<Copy className="w-3.5 h-3.5" />
<span>Copiar código</span>
</>
)}
</button>
</div>
<pre className="bg-slate-950 text-slate-200 font-mono text-xs sm:text-sm p-4 rounded-2xl overflow-x-auto border border-slate-800 leading-relaxed">
{codigoAtual}
</pre>
</div>
{/* Dica Pro */}
<div className="p-3.5 rounded-2xl bg-amber-500/10 border border-amber-500/30 flex items-start gap-2.5">
<Wrench className="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
<p className="text-xs text-amber-900 dark:text-amber-200">
<strong>Dica Pro de Engenharia:</strong> {erro.dicaPro}
</p>
</div>
</div>
)}
</article>
);
})
)}
</section>
{/* 4. CHECKLIST DE PREVENÇÃO EM HARDWARE E FIRMWARE */}
<section className="mt-16 bg-gradient-to-br from-slate-900 to-slate-950 text-white rounded-3xl p-6 sm:p-10 border border-slate-800 shadow-xl">
<div className="flex items-center gap-3 mb-6">
<div className="p-3 rounded-2xl bg-amber-500/20 text-amber-400 border border-amber-500/30">
<ShieldAlert className="w-6 h-6" />
</div>
<div>
<h2 className="text-xl sm:text-2xl font-display font-bold">
Checklist de Prevenção para Produção
</h2>
<p className="text-xs sm:text-sm text-slate-400">
Itens essenciais para validar antes de enviar seu hardware ou firmware para campo.
</p>
</div>
</div>
<div className="grid md:grid-cols-2 gap-6 text-xs sm:text-sm">
<div className="space-y-3 bg-slate-800/50 p-5 rounded-2xl border border-slate-700/60">
<h3 className="font-bold text-amber-400 uppercase tracking-wider text-xs flex items-center gap-2">
<Zap className="w-4 h-4" /> Checklist de Hardware & Alimentação
</h3>
<ul className="space-y-2.5 text-slate-300">
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Capacitor de 10µF ~ 100µF Low-ESR no VCC + cerâmico de 100nF próximo ao chip.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>LDO dimensionado para suportar picos de corrente de no mínimo 600mA a 1A com folga térmica.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Nenhum periférico forçando níveis lógicos em Strapping Pins no momento do boot.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Capacitor de 10µF ou 1µF no pino EN (RST) para auto-reset estável no upload.</span>
</li>
</ul>
</div>
<div className="space-y-3 bg-slate-800/50 p-5 rounded-2xl border border-slate-700/60">
<h3 className="font-bold text-purple-400 uppercase tracking-wider text-xs flex items-center gap-2">
<Layers className="w-4 h-4" /> Checklist de Firmware & FreeRTOS
</h3>
<ul className="space-y-2.5 text-slate-300">
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Todas as tarefas FreeRTOS cedem CPU através de vTaskDelay ou filas/semáforos.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Todas as ISRs decoradas com `IRAM_ATTR` e sem chamadas bloqueantes.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Sem uso contínuo de objetos `String` do Arduino em loops para evitar fragmentação de Heap.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Sincronização de horário SNTP antes de iniciar conexões criptografadas TLS/HTTPS.</span>
</li>
</ul>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}