# app.py # CIA – Central de Impressões APOGEU # Streamlit (Hugging Face Spaces) # # Versão "nível Apple" consolidada: # ✅ Sem sidebar: Tipo/Acesso/Solicitante/Unidade abaixo do cabeçalho # ✅ Impressão + Acabamentos (acabamentos abaixo; total = soma) # ✅ Quantidade de acabamentos = Tiragem (não pergunta quantidade) # ✅ Regra OFICIAL: 1x1 = 1x0 × 2 (base sempre por página) # ✅ Upload múltiplo obrigatório (1+ arquivos) — base64 só no clique/execução (evita rerun pesado) # ✅ Persistência de contato: Nome/E-mail não somem ao anexar arquivos # ✅ Segurança: não exibe links de arquivos após envio (e GAS deve estar sem links no e-mail) # ✅ Bloqueio REAL anti-reenvio: trava otimista + sending inflight (sem “segundo envio”) # ✅ Compatibilidade: envia tipo e tipo_pedido from __future__ import annotations import base64 from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional, Tuple import pandas as pd import requests import streamlit as st # ========================= # Config / Assets # ========================= BASE_DIR = Path(__file__).resolve().parent HEADER_IMG = BASE_DIR / "CIA - Central de Impressões APOGEU.png" EXCEL_BASE = BASE_DIR / "vale-o-que-custa-MODELO.xlsx" UNIDADES = [ "Global School Cidade Alta", "Global School Ferreira Guimarães", "Santo Antônio 1", "Santo Antônio 2", "Zona Norte", "Central", ] SOLICITANTES = ["Unidade", "Central"] TIPOS = [ "Material Didático", "Avaliação/Cartão Resposta", "Material Extra", "Carta Dourada", "Certificado A+", "Certificado Olímpico", ] # ========================= # Helpers # ========================= def get_webhook_url() -> Optional[str]: try: return st.secrets.get("WEBHOOK_URL", None) except Exception: return None def money_br(v: float) -> str: return f"R$ {v:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".") def split_multi(value: Any) -> list[str]: if value is None: return [] s = str(value).strip() if not s: return [] return [p.strip() for p in s.split(";") if p.strip()] def verso_label(code: str) -> str: c = (code or "").strip().lower() if c == "1x0": return "1 face (1x0)" if c == "1x1": return "Frente e verso (1x1)" return code def render_header_img(path: Path) -> None: """Header definitivo sem corte usando HTML base64 e object-fit: contain.""" if not path.exists(): st.warning( f"Imagem de cabeçalho não encontrada: **{path.name}**.\n\n" "Confira se o arquivo está na raiz do repositório e com o nome EXATO." ) return b = path.read_bytes() b64 = base64.b64encode(b).decode("utf-8") st.markdown( f"""
""", unsafe_allow_html=True, ) def uploads_to_payload(uploads) -> List[Dict[str, Any]]: """ Converte UploadedFiles em lista serializável. IMPORTANTE: chamar somente na execução do envio (evita rerun pesado). """ out: List[Dict[str, Any]] = [] for up in uploads or []: raw = up.getvalue() out.append( { "filename": up.name, "mimetype": up.type or "application/octet-stream", "content_base64": base64.b64encode(raw).decode("utf-8"), "size_bytes": len(raw), } ) return out @st.cache_data(show_spinner=False) def load_excel_tables(xlsx_path: Path) -> Tuple[pd.DataFrame, pd.DataFrame]: if not xlsx_path.exists(): return pd.DataFrame(), pd.DataFrame() df_imp = pd.read_excel(xlsx_path, sheet_name="impressao") df_acab = pd.read_excel(xlsx_path, sheet_name="acabamentos") df_imp.columns = [str(c).strip() for c in df_imp.columns] df_acab.columns = [str(c).strip() for c in df_acab.columns] if "ativo" in df_imp.columns: df_imp = df_imp[df_imp["ativo"].astype(bool)] if "ativo" in df_acab.columns: df_acab = df_acab[df_acab["ativo"].astype(bool)] return df_imp, df_acab def filter_by_solicitante(df: pd.DataFrame, solicitante_tipo: str) -> pd.DataFrame: if df.empty: return df if "Solicitante" not in df.columns: return df return df[df["Solicitante"].astype(str).str.strip().str.lower() == solicitante_tipo.strip().lower()] def post_to_webhook( payload: Dict[str, Any], webhook_url: str, timeout: int = 240 ) -> Tuple[bool, str, int, str, Optional[Dict[str, Any]]]: """ Regra: Só OK se JSON tiver status="ok". """ try: r = requests.post(webhook_url, json=payload, timeout=timeout) raw = r.text or "" parsed: Optional[Dict[str, Any]] = None try: parsed = r.json() except Exception: parsed = None if not (200 <= r.status_code < 300): return False, f"Falha HTTP {r.status_code}", r.status_code, raw[:2000], parsed if not isinstance(parsed, dict) or "status" not in parsed: return False, "Servidor não retornou JSON válido com status.", r.status_code, raw[:2000], parsed if str(parsed.get("status")).lower() != "ok": return False, f"Servidor respondeu erro: {parsed.get('message','(sem mensagem)')}", r.status_code, raw[:2000], parsed return True, "Pedido confirmado pelo servidor.", r.status_code, raw[:2000], parsed except Exception as e: return False, f"Erro ao enviar: {e}", 0, "", None def build_resumo_pedido(sim_imp: Dict[str, Any], acab_list: List[Dict[str, Any]], tipo: str) -> str: verso = sim_imp.get("verso", "") tiragem = int(sim_imp.get("tiragem", 0) or 0) paginas_por_copia = int(sim_imp.get("paginas_por_copia", 0) or 0) paginas_totais = int(sim_imp.get("paginas_totais", 0) or 0) base = f"Tipo: **{tipo}**\n\n{tiragem} cópias • {paginas_por_copia} página(s) por cópia • total {paginas_totais} página(s)" imp = ( f"Impressão: **{sim_imp.get('tipo_papel','')}** • **{sim_imp.get('formato','')}** • " f"**{sim_imp.get('cor','')}** • verso **{verso}**" ) if acab_list: acabs = ", ".join([a.get("acabamento", "") for a in acab_list]) acab_txt = f"Acabamentos: **{acabs}** (quantidade = tiragem)" else: acab_txt = "Acabamentos: **nenhum**" return f"{base}\n\n{imp}\n\n{acab_txt}" # ========================= # Premium UI # ========================= st.set_page_config(page_title="CIA - Central de Impressões APOGEU", layout="wide") st.markdown( """ """, unsafe_allow_html=True, ) # ========================= # Estado # ========================= st.session_state.setdefault("step", 1) st.session_state.setdefault("perfil", "") st.session_state.setdefault("solicitante_tipo", SOLICITANTES[0]) st.session_state.setdefault("unidade", UNIDADES[0]) st.session_state.setdefault("tipo_categoria", TIPOS[0]) # seleção dinâmica impressão st.session_state.setdefault("tipo_papel", "") st.session_state.setdefault("formato", "") st.session_state.setdefault("cor", "") st.session_state.setdefault("verso_ui", "") # volumes st.session_state.setdefault("paginas_por_copia", 1) st.session_state.setdefault("tiragem", 1) # acabamentos st.session_state.setdefault("acabamentos_sel", []) # resultados st.session_state.setdefault("sim_imp", {}) st.session_state.setdefault("sim_acab", []) st.session_state.setdefault("tot_impressao", 0.0) st.session_state.setdefault("tot_acabamentos", 0.0) st.session_state.setdefault("tot_geral", 0.0) # solicitante (inputs) st.session_state.setdefault("solicitante_nome", "") st.session_state.setdefault("solicitante_email", "") st.session_state.setdefault("observacoes", "") # solicitante (salvos para etapa 3) st.session_state.setdefault("solicitante_nome_saved", "") st.session_state.setdefault("solicitante_email_saved", "") st.session_state.setdefault("observacoes_saved", "") # retorno envio st.session_state.setdefault("last_send_debug", None) st.session_state.setdefault("codigo_pedido", "") st.session_state.setdefault("envio_ok", False) # bloqueio real anti-reenvio + anti-duplo clique st.session_state.setdefault("sending", False) # envio em andamento st.session_state.setdefault("sent_lock", False) # pedido já registrado st.session_state.setdefault("sent_codigo", "") # ========================= # Topo # ========================= render_header_img(HEADER_IMG) st.markdown("## CIA – Central de Impressões APOGEU") st.caption("Simule custos, valide e envie solicitações com anexos e confirmação por e-mail.") df_imp, df_acab = load_excel_tables(EXCEL_BASE) # Stepper progress = {1: 0.33, 2: 0.66, 3: 1.0}.get(int(st.session_state.step), 0.33) st.progress(progress) st.markdown(f'Etapa {st.session_state.step} de 3', unsafe_allow_html=True) # Bloco inicial st.markdown("---") c0, c1, c2, c3 = st.columns([1.25, 1, 1, 1]) with c0: st.markdown('
', unsafe_allow_html=True) st.markdown('
Tipo
', unsafe_allow_html=True) st.selectbox("Selecione o tipo", TIPOS, key="tipo_categoria") st.markdown("
", unsafe_allow_html=True) with c1: st.markdown('
', unsafe_allow_html=True) st.markdown('
Acesso
', unsafe_allow_html=True) st.selectbox( "Quem é você?", ["", "Coordenação", "Secretaria", "Professores", "Administrativo", "Direção"], key="perfil", ) st.markdown("
", unsafe_allow_html=True) with c2: st.markdown('
', unsafe_allow_html=True) st.markdown('
Solicitante
', unsafe_allow_html=True) st.selectbox("Selecione o solicitante", SOLICITANTES, key="solicitante_tipo") st.markdown("
", unsafe_allow_html=True) with c3: st.markdown('
', unsafe_allow_html=True) st.markdown('
Unidade
', unsafe_allow_html=True) st.selectbox("Selecione a unidade", UNIDADES, key="unidade") st.markdown("
", unsafe_allow_html=True) perfil_ok = bool(st.session_state.perfil.strip()) solicitante_tipo = st.session_state.solicitante_tipo df_imp_f = filter_by_solicitante(df_imp, solicitante_tipo) df_acab_f = filter_by_solicitante(df_acab, solicitante_tipo) # callbacks instantâneos def on_change_tipo_papel(): st.session_state.formato = "" st.session_state.cor = "" st.session_state.verso_ui = "" def on_change_formato(): st.session_state.cor = "" st.session_state.verso_ui = "" def on_change_cor(): st.session_state.verso_ui = "" # ========================= # ETAPA 1 — Simulação # ========================= if st.session_state.step == 1: st.markdown("---") st.markdown("### 1) Simulação") if not perfil_ok: st.info("Selecione **Quem é você?** para liberar a simulação.") st.stop() if df_imp_f.empty: st.error("Não há opções de impressão disponíveis para este solicitante.") st.stop() tipo_papel_opts = sorted(df_imp_f["tipo_papel"].dropna().astype(str).unique().tolist()) if st.session_state.tipo_papel not in tipo_papel_opts: st.session_state.tipo_papel = tipo_papel_opts[0] if tipo_papel_opts else "" base_papel = df_imp_f[df_imp_f["tipo_papel"].astype(str) == str(st.session_state.tipo_papel)] formato_opts = sorted(base_papel["formato"].dropna().astype(str).unique().tolist()) if st.session_state.formato not in formato_opts: st.session_state.formato = formato_opts[0] if formato_opts else "" base_formato = base_papel[base_papel["formato"].astype(str) == str(st.session_state.formato)] cor_opts = sorted(base_formato["cor"].dropna().astype(str).unique().tolist()) if st.session_state.cor not in cor_opts: st.session_state.cor = cor_opts[0] if cor_opts else "" base_cor = base_formato[base_formato["cor"].astype(str) == str(st.session_state.cor)] verso_raw_opts: List[str] = [] for v in base_cor["verso"].dropna().tolist(): verso_raw_opts.extend(split_multi(v)) verso_raw_opts = sorted(list(dict.fromkeys(verso_raw_opts))) or ["1x0"] verso_map = {verso_label(v): v for v in verso_raw_opts} verso_ui_opts = list(verso_map.keys()) if st.session_state.verso_ui not in verso_ui_opts: st.session_state.verso_ui = verso_ui_opts[0] if verso_ui_opts else "" st.markdown('
', unsafe_allow_html=True) topA, topB = st.columns([2, 1]) with topA: st.markdown('
', unsafe_allow_html=True) st.markdown('
Impressão
', unsafe_allow_html=True) s1, s2, s3 = st.columns(3) with s1: st.selectbox("Tipo / Papel", tipo_papel_opts, key="tipo_papel", on_change=on_change_tipo_papel) st.selectbox("Formato", formato_opts, key="formato", on_change=on_change_formato) with s2: st.selectbox("Cor", cor_opts, key="cor", on_change=on_change_cor) st.selectbox("Verso", verso_ui_opts, key="verso_ui") with s3: st.number_input("Páginas (por cópia/conjunto)", min_value=1, step=1, key="paginas_por_copia") st.number_input("Tiragem (quantidade de cópias)", min_value=1, step=1, key="tiragem") st.caption("Regra oficial: **1x1 = 1x0 × 2** (base por página).") st.markdown("
", unsafe_allow_html=True) with topB: st.markdown('
', unsafe_allow_html=True) st.markdown('
Acabamentos (opcional)
', unsafe_allow_html=True) if df_acab_f.empty: st.info("Sem acabamentos disponíveis para este solicitante.") st.session_state.acabamentos_sel = [] else: acabamento_opts = sorted(df_acab_f["acabamento"].dropna().astype(str).unique().tolist()) st.multiselect( "Selecione os acabamentos", options=acabamento_opts, default=st.session_state.acabamentos_sel, key="acabamentos_sel", help="Quantidade de cada acabamento = tiragem.", ) st.caption("Cálculo: preço unitário × tiragem (para cada item).") st.markdown("
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown("---") if st.button("Calcular", type="primary", use_container_width=True): verso_code = verso_map.get(st.session_state.verso_ui, st.session_state.verso_ui) pick = df_imp_f[ (df_imp_f["tipo_papel"].astype(str) == str(st.session_state.tipo_papel)) & (df_imp_f["formato"].astype(str) == str(st.session_state.formato)) & (df_imp_f["cor"].astype(str) == str(st.session_state.cor)) ].copy() def verso_contains(cell: Any) -> bool: return verso_code in split_multi(cell) pick = pick[pick["verso"].apply(verso_contains)] if pick.empty: st.error("Não encontrei um preço no Excel para a combinação selecionada.") else: unit_base = float(pick.iloc[0]["preco_unit"]) paginas_totais = int(st.session_state.paginas_por_copia) * int(st.session_state.tiragem) multiplicador = 2.0 if (verso_code or "").strip().lower() == "1x1" else 1.0 unit_aplicado = unit_base * multiplicador tot_imp = paginas_totais * unit_aplicado st.session_state.sim_imp = { "tipo_papel": str(st.session_state.tipo_papel), "formato": str(st.session_state.formato), "cor": str(st.session_state.cor), "verso": str(verso_code), "preco_unit_base": float(unit_base), "multiplicador_verso": float(multiplicador), "preco_unit_aplicado": float(unit_aplicado), "paginas_por_copia": int(st.session_state.paginas_por_copia), "tiragem": int(st.session_state.tiragem), "paginas_totais": int(paginas_totais), "base_cobranca": "pagina", "total_impressao": float(tot_imp), } itens_acab: List[Dict[str, Any]] = [] tot_acab = 0.0 if not df_acab_f.empty and st.session_state.acabamentos_sel: for nome_acab in st.session_state.acabamentos_sel: pick_a = df_acab_f[df_acab_f["acabamento"].astype(str) == str(nome_acab)] if pick_a.empty: continue unit_a = float(pick_a.iloc[0]["preco_unit"]) subtotal = unit_a * int(st.session_state.tiragem) itens_acab.append( { "acabamento": str(nome_acab), "preco_unit": float(unit_a), "quantidade": int(st.session_state.tiragem), "subtotal": float(subtotal), } ) tot_acab += subtotal st.session_state.sim_acab = itens_acab st.session_state.tot_impressao = float(tot_imp) st.session_state.tot_acabamentos = float(tot_acab) st.session_state.tot_geral = float(tot_imp + tot_acab) # nova simulação = novo pedido (reseta envio/travas) st.session_state.envio_ok = False st.session_state.codigo_pedido = "" st.session_state.last_send_debug = None st.session_state.sending = False st.session_state.sent_lock = False st.session_state.sent_codigo = "" if st.session_state.sim_imp: st.markdown("---") c1, c2, c3 = st.columns(3) c1.metric("Total Impressão", money_br(st.session_state.tot_impressao)) c2.metric("Total Acabamentos", money_br(st.session_state.tot_acabamentos)) c3.metric("Total Geral", money_br(st.session_state.tot_geral)) st.markdown('
', unsafe_allow_html=True) st.markdown("#### Resumo do pedido") st.write(build_resumo_pedido(st.session_state.sim_imp, st.session_state.sim_acab, st.session_state.tipo_categoria)) st.markdown("
", unsafe_allow_html=True) st.markdown("---") if st.button("Continuar", type="primary", use_container_width=True): st.session_state.step = 2 st.rerun() # ========================= # ETAPA 2 — Dados do solicitante # ========================= if st.session_state.step == 2: st.markdown("---") st.markdown("### 2) Dados do solicitante") if not st.session_state.sim_imp: st.warning("Você precisa concluir a simulação primeiro.") if st.button("Voltar para Simulação"): st.session_state.step = 1 st.rerun() st.stop() left, right = st.columns([2, 1]) with left: st.markdown('
', unsafe_allow_html=True) st.markdown('
Contato
', unsafe_allow_html=True) st.text_input("Nome completo", key="solicitante_nome") st.text_input("E-mail", key="solicitante_email") st.text_area("Observações (opcional)", key="observacoes", height=110) st.markdown("
", unsafe_allow_html=True) with right: st.markdown('
', unsafe_allow_html=True) st.markdown('
Resumo
', unsafe_allow_html=True) st.write(build_resumo_pedido(st.session_state.sim_imp, st.session_state.sim_acab, st.session_state.tipo_categoria)) st.markdown("
", unsafe_allow_html=True) st.markdown("---") b1, b2 = st.columns(2) with b1: if st.button("Voltar", use_container_width=True): st.session_state.step = 1 st.rerun() with b2: can_next = bool(st.session_state.solicitante_nome.strip()) and ("@" in st.session_state.solicitante_email.strip()) if st.button("Ir para envio", type="primary", use_container_width=True, disabled=not can_next): st.session_state.solicitante_nome_saved = st.session_state.solicitante_nome.strip() st.session_state.solicitante_email_saved = st.session_state.solicitante_email.strip() st.session_state.observacoes_saved = st.session_state.observacoes.strip() st.session_state.step = 3 st.rerun() # ========================= # ETAPA 3 — Envio # ========================= if st.session_state.step == 3: st.markdown("---") st.markdown("### 3) Enviar solicitação") webhook_url = get_webhook_url() if not webhook_url: st.warning( "Segredo **WEBHOOK_URL** não configurado.\n\n" "No Hugging Face: Settings → Secrets → New secret\n" "- Name: WEBHOOK_URL\n" "- Value: URL /exec do Apps Script\n" ) # fallback se chegou aqui sem congelar if not st.session_state.solicitante_nome_saved.strip() and st.session_state.solicitante_nome.strip(): st.session_state.solicitante_nome_saved = st.session_state.solicitante_nome.strip() if not st.session_state.solicitante_email_saved.strip() and st.session_state.solicitante_email.strip(): st.session_state.solicitante_email_saved = st.session_state.solicitante_email.strip() if not st.session_state.observacoes_saved.strip() and st.session_state.observacoes.strip(): st.session_state.observacoes_saved = st.session_state.observacoes.strip() left, right = st.columns([2, 1]) with left: st.markdown('
', unsafe_allow_html=True) st.markdown('
Anexos (obrigatório)
', unsafe_allow_html=True) uploads = st.file_uploader( "Envie um ou mais arquivos do pedido", type=None, accept_multiple_files=True, help="Obrigatório enviar pelo menos 1 arquivo.", ) if uploads and len(uploads) > 0: st.success(f"{len(uploads)} arquivo(s) pronto(s) para envio.") st.write("**Arquivos selecionados:**") for u in uploads: st.write(f"• {u.name}") else: st.info("Anexe ao menos 1 arquivo para habilitar o envio.") st.markdown("
", unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
Revisão
', unsafe_allow_html=True) st.metric("Total Geral", money_br(st.session_state.tot_geral)) st.write(build_resumo_pedido(st.session_state.sim_imp, st.session_state.sim_acab, st.session_state.tipo_categoria)) st.markdown("
", unsafe_allow_html=True) with right: st.markdown('
', unsafe_allow_html=True) st.markdown('
Identificação
', unsafe_allow_html=True) st.write(f"**Tipo:** {st.session_state.tipo_categoria}") st.write(f"**Perfil:** {st.session_state.perfil}") st.write(f"**Solicitante:** {st.session_state.solicitante_tipo}") st.write(f"**Unidade:** {st.session_state.unidade}") st.write(f"**Nome:** {st.session_state.solicitante_nome_saved}") st.write(f"**E-mail:** {st.session_state.solicitante_email_saved}") st.markdown("
", unsafe_allow_html=True) st.markdown("---") # Botões de navegação (voltar desabilitado após sucesso) if st.button("Voltar", use_container_width=True, disabled=st.session_state.sent_lock or st.session_state.sending): st.session_state.step = 2 st.rerun() if st.session_state.sent_lock: st.info( f"Pedido já registrado: **{st.session_state.sent_codigo}**. " "Para enviar outro pedido, clique em **Nova solicitação**." ) # Pode enviar? can_send = ( bool(webhook_url) and bool(uploads and len(uploads) > 0) and bool(st.session_state.sim_imp) and bool(st.session_state.solicitante_nome_saved.strip()) and ("@" in st.session_state.solicitante_email_saved.strip()) and (not st.session_state.sent_lock) and (not st.session_state.sending) ) send = st.button( "Enviar agora", type="primary", use_container_width=True, disabled=not can_send, ) # TRAVA OTIMISTA imediata: mata o botão antes de qualquer POST/base64 if send: st.session_state.sending = True st.session_state.sent_lock = True st.session_state.sent_codigo = "Processando…" st.rerun() # EXECUÇÃO DO ENVIO (após rerun, com sending=True) if st.session_state.sending: # Revalida pré-condições (segurança) pre_ok = ( bool(webhook_url) and bool(uploads and len(uploads) > 0) and bool(st.session_state.sim_imp) and bool(st.session_state.solicitante_nome_saved.strip()) and ("@" in st.session_state.solicitante_email_saved.strip()) ) if not pre_ok: st.session_state.sending = False st.session_state.sent_lock = False st.session_state.sent_codigo = "" st.warning("Faltam dados para envio. Verifique anexos e contato.") st.stop() with st.spinner("Enviando… registrando na planilha e salvando anexos…"): arquivos_payload = uploads_to_payload(uploads) payload = { "timestamp": datetime.now().isoformat(timespec="seconds"), "perfil": st.session_state.perfil, # compatibilidade (novo + antigo) "tipo": st.session_state.tipo_categoria, "tipo_pedido": st.session_state.tipo_categoria, "solicitante_tipo": st.session_state.solicitante_tipo, "unidade": st.session_state.unidade, "solicitante_nome": st.session_state.solicitante_nome_saved.strip(), "solicitante_email": st.session_state.solicitante_email_saved.strip(), "observacoes": st.session_state.observacoes_saved.strip(), "pedido": { "impressao": st.session_state.sim_imp, "acabamentos": st.session_state.sim_acab, "totais": { "impressao": float(st.session_state.tot_impressao), "acabamentos": float(st.session_state.tot_acabamentos), "geral": float(st.session_state.tot_geral), }, }, "arquivos": arquivos_payload, } ok, msg, code, raw, parsed = post_to_webhook(payload, webhook_url) st.session_state.last_send_debug = {"ok": ok, "msg": msg, "http_code": code, "raw": raw, "parsed": parsed} if ok: st.session_state.envio_ok = True st.session_state.codigo_pedido = str(parsed.get("codigo_pedido", "") or "").strip() st.session_state.sent_codigo = st.session_state.codigo_pedido or "Pedido confirmado" # mantém sent_lock = True else: st.session_state.envio_ok = False st.session_state.codigo_pedido = "" st.session_state.sent_lock = False st.session_state.sent_codigo = "" st.session_state.sending = False st.rerun() # Resultado pós-envio if st.session_state.envio_ok: st.markdown("---") st.success("Solicitação enviada e confirmada.") if st.session_state.codigo_pedido: st.markdown( f'**Código do pedido:** {st.session_state.codigo_pedido}', unsafe_allow_html=True, ) # Segurança: não exibir links. Se o GAS devolver files, só mostra nomes (opcional). parsed = (st.session_state.last_send_debug or {}).get("parsed") if isinstance(parsed, dict) and parsed.get("files"): with st.expander("Arquivos registrados", expanded=False): for f in parsed["files"]: st.write(f"• {f.get('name','arquivo')}") st.markdown("---") col_a, col_b = st.columns(2) with col_a: if st.button("Nova solicitação", use_container_width=True): st.session_state.step = 1 # reseta simulação (mantém dados de acesso pra UX premium) st.session_state.sim_imp = {} st.session_state.sim_acab = [] st.session_state.tot_impressao = 0.0 st.session_state.tot_acabamentos = 0.0 st.session_state.tot_geral = 0.0 st.session_state.paginas_por_copia = 1 st.session_state.tiragem = 1 st.session_state.acabamentos_sel = [] st.session_state.envio_ok = False st.session_state.codigo_pedido = "" st.session_state.last_send_debug = None st.session_state.sending = False st.session_state.sent_lock = False st.session_state.sent_codigo = "" st.rerun() with col_b: if st.button("Limpar contato", use_container_width=True): st.session_state.solicitante_nome = "" st.session_state.solicitante_email = "" st.session_state.observacoes = "" st.session_state.solicitante_nome_saved = "" st.session_state.solicitante_email_saved = "" st.session_state.observacoes_saved = "" st.rerun() elif st.session_state.last_send_debug and not st.session_state.envio_ok: st.error("Não foi possível confirmar o envio com o servidor.") with st.expander("Retorno técnico", expanded=True): st.write(st.session_state.last_send_debug)