https://github.com/codigoencasa/builderbot
https://pt.botlibre.com/
https://4linux.com.br/consultoria/case/chatbot-weni-livechat-rocket-api-whatsapp/
/*
Atestado médico
Um atestado médico é um documento emitido por um profissional de saúde, que confirma a condição de saúde de um paciente. Ele pode ser utilizado para justificar uma ausência no trabalho, na escola, em eventos, entre outros, devido a razões médicas.

Normalmente, um atestado médico inclui informações como o nome do paciente, a data de emissão, a descrição da condição médica, o período de repouso recomendado (se aplicável) e a assinatura ou carimbo do profissional de saúde.

Declaração de comparecimento
É um documento emitido por uma instituição, como um hospital, clínica ou consultório médico, confirmando que uma pessoa compareceu a uma consulta, exame ou procedimento em um determinado dia e hora.

Ao contrário de um atestado médico, não fornece informações sobre a condição médica do paciente, apenas confirma que ele esteve presente em uma consulta ou procedimento específico.


Os principais tipos de declaração de comparecimento na saúde são:

Declaração de Comparecimento para Consulta Médica: Este é o tipo mais comum, emitido para atestar a presença do paciente em uma consulta com médico, dentista, psicólogo, nutricionista, etc. Geralmente, informa a data, hora de início e fim da consulta, nome do paciente e do profissional de saúde, e identificação da instituição.

Declaração de Comparecimento para Exame: Utilizada para comprovar a realização de exames laboratoriais (sangue, urina, fezes), exames de imagem (radiografia, ultrassom, ressonância magnética) ou outros procedimentos diagnósticos. Assim como na consulta, deve conter as informações básicas de identificação e do procedimento.

Declaração de Comparecimento para Procedimento: Abrange a comprovação da presença para pequenos procedimentos ambulatoriais, como curativos, aplicações de medicamentos, retirada de pontos, fisioterapia, entre outros.

Declaração de Comparecimento para Acompanhante: Em muitos casos, especialmente quando o paciente é menor de idade, idoso, ou possui alguma necessidade especial, é permitido e até necessário que um acompanhante esteja presente. Essa declaração é emitida para o acompanhante, atestando sua permanência na unidade de saúde durante a consulta ou procedimento do paciente.

Declaração de Comparecimento para Internação/Alta Hospitalar: Embora o foco seja mais na alta hospitalar (que já comprova a permanência), declarações específicas podem ser emitidas para atestar o período de internação de um paciente, geralmente para fins trabalhistas ou previdenciários.

Declaração de Comparecimento para Vacinação: Usada para comprovar que uma pessoa compareceu a um posto de saúde ou clínica para receber uma vacina.

Declaração de Comparecimento em Atendimento de Emergência/Urgência: Emitida para pacientes que buscaram atendimento em pronto-socorro ou emergência, independentemente de terem sido internados ou não.

Informações Comuns em uma Declaração de Comparecimento:
Independentemente do tipo, uma declaração de comparecimento geralmente contém as seguintes informações:

Identificação da Instituição de Saúde: Nome completo, CNPJ e endereço.
Dados do Paciente: Nome completo, número do documento de identificação (RG, CPF).
Dados do Acompanhante (se houver): Nome completo e número do documento de identificação.
Data e Hora: Data e horário de início e término do atendimento, consulta, exame ou procedimento.
Tipo de Atendimento: Descrição breve do motivo do comparecimento (ex: "consulta médica", "exame de imagem", "atendimento de emergência").
Nome e Assinatura do Profissional: Identificação e assinatura do médico, enfermeiro ou responsável pela emissão do documento.

O que deve constar na declaração de comparecimento?
A falta de informações essenciais pode comprometer a validade do documento, dificultar o controle de ponto, impactar a gestão do absenteísmo e prejudicar a aplicação correta das políticas de faltas justificadas.

Para ser aceita, a declaração deve conter os seguintes elementos obrigatórios:  

Nome completo do colaborador;
Datas e horários de comparecimento (início e término);
Motivo da presença (consulta médica, audiência, doação, etc.);
Nome da instituição ou profissional responsável;
Assinatura e carimbo do responsável pela emissão;
CNPJ ou CRM (quando aplicável).


Você me pediu para listar os prazos legais para alguns documentos médicos, então vamos lá:

Prazos Legais para Documentos Médicos no Brasil
É importante lembrar que, embora existam prazos gerais, a validade de um documento médico pode ser influenciada por fatores como a finalidade do documento, o tipo de medicamento (no caso de receitas) e as especificidades da condição de saúde do paciente.

1. Receita Médica
A validade da receita médica varia de acordo com o tipo de medicamento prescrito:

Receita Simples: Geralmente, possui validade de 30 dias a partir da data de sua emissão.
Receita de Controle Especial (duas vias, como lista C1): Também tem validade de 30 dias a partir da data de sua emissão. A quantidade prescrita pode ser para até 60 dias de tratamento em alguns casos.
Receita de Antimicrobianos (antibióticos): Possui validade de 10 dias a partir da data de sua emissão. Para tratamentos prolongados, a receita pode ser utilizada para aquisições posteriores dentro de um período de 90 dias, desde que contenha a indicação de uso contínuo.
Notificações de Receita "A" (amarela - entorpecentes e psicotrópicos): Válida por 30 dias a contar da data de sua emissão, em todo o Território Nacional. A quantidade prescrita é para até 30 dias de tratamento.
Notificações de Receita "B" e "B2" (azul - psicotrópicos e anorexígenos): Válida por 30 dias a contar da data de sua emissão, mas somente dentro da Unidade Federativa que concedeu a numeração. A quantidade prescrita é para até 30 ou 60 dias de tratamento, dependendo da lista e regulamentação específica.
Receitas de medicamentos de uso contínuo (doenças crônicas): O prazo de validade pode ser de 180 dias (6 meses), contados da data de emissão, com quantidade suficiente para tratamento por 60 dias, em alguns serviços públicos.
2. Atestado Médico
O atestado médico, por si só, não tem um "prazo de validade" fixo após sua emissão, mas sim uma data de início e término da incapacidade do paciente, quando aplicável.

Para fins trabalhistas: O atestado deve ser entregue ao empregador dentro de um prazo estipulado pela empresa ou, na ausência de regra interna, em até 48 horas após o retorno ao trabalho.
Afastamento do trabalho: Os primeiros 15 dias de afastamento são responsabilidade do empregador. A partir do 16º dia, a responsabilidade de pagamento do salário passa a ser do INSS, que exigirá a documentação adequada para a concessão do auxílio-doença.
Conteúdo: Para ser válido, o atestado deve conter o nome completo do paciente, o tempo de afastamento recomendado (em dias), a data de emissão e a identificação clara do médico (nome, CRM/UF e assinatura).
3. Relatório Médico e Laudos
A validade de relatórios e laudos médicos pode variar consideravelmente, dependendo da sua finalidade:

Para o INSS (auxílio-doença, benefícios): Laudos periciais e relatórios médicos para o INSS são considerados válidos por até 90 dias a partir da data de sua emissão.
Para processos judiciais: Em contextos judiciais, a validade pode ser estendida para até 180 dias.
Laudos para deficiência permanente: Recentemente, leis têm sido aprovadas em diversas esferas para garantir a validade indeterminada de laudos médicos que atestam deficiência permanente, evitando a necessidade de revalidação constante para acesso a direitos e benefícios.
Laudos de exames: Embora o laudo em si seja um registro daquele momento, a atualização da informação é crucial. Equipamentos antigos ou sem manutenção podem gerar resultados imprecisos. A instituição é obrigada a guardar o laudo por, no mínimo, 20 anos (prazo de guarda do prontuário).
4. Risco Cirúrgico (Avaliação Pré-Operatória)
A avaliação de risco cirúrgico, que inclui exames e laudos, tem um prazo de validade mais flexível, dependendo da condição do paciente e do tipo de cirurgia:

Regra geral: Os resultados da avaliação pré-operatória são considerados válidos por até 6 meses a 1 ano, desde que o paciente se mantenha assintomático ou não apresente agravamento dos sintomas da sua condição de saúde.
Variações: Para pacientes com comorbidades graves ou que apresentem alterações em seu quadro clínico, o médico pode solicitar nova avaliação ou exames mais recentes, mesmo que dentro do prazo "geral".
Observação: A Resolução CFM nº 2.381/2024 estabelece normas éticas para a emissão de documentos médicos, reforçando que eles gozam de presunção de veracidade e devem conter, minimamente, a identificação do médico (nome e CRM/UF), do paciente (nome e CPF, se houver), data e demais informações relevantes, além da assinatura e carimbo do profissional.

É sempre recomendável que você consulte o profissional de saúde ou a instituição que solicitou o documento para confirmar o prazo exato de validade e quaisquer requisitos específicos, pois a legislação pode ter nuances e interpretações pontuais.

Documentos de Saúde e Seus Prazos de Validade Conforme a Legislação Brasileira
No Brasil, diversos documentos e licenças relacionados à saúde possuem prazos de validade específicos, estabelecidos por diferentes órgãos reguladores, como a Agência Nacional de Vigilância Sanitária (ANVISA), o Ministério da Saúde, e conselhos profissionais. É fundamental estar atento a esses vencimentos para garantir a conformidade legal e o bom funcionamento de estabelecimentos e a atuação de profissionais da área.

Abaixo, listo os principais documentos e seus respectivos vencimentos, de acordo com a legislação vigente:

Para Estabelecimentos de Saúde (Clínicas, Hospitais, Laboratórios, Farmácias, etc.)
Alvará Sanitário (Licença de Funcionamento): Este é um dos documentos mais importantes e é emitido pela vigilância sanitária municipal ou estadual.

Vencimento: Geralmente, a validade do Alvará Sanitário é de 1 (um) ano, exigindo renovação anual. No entanto, o prazo exato pode variar ligeiramente entre os municípios e estados, por isso é crucial verificar a legislação local.
Autorização de Funcionamento (AFE) da ANVISA: Necessária para empresas que fabricam, distribuem, importam, armazenam ou transportam medicamentos, produtos para a saúde, cosméticos, saneantes e alimentos.

Vencimento: A AFE não possui um prazo de validade fixo, mas é necessária a revalidação periódica ou a comunicação de alterações à ANVISA. A ANVISA pode estabelecer critérios de revalidação baseados em risco sanitário ou mudanças regulatórias.
Autorização Especial (AE) da ANVISA: Exigida para empresas que exercem atividades com substâncias ou medicamentos sujeitos a controle especial (portaria SVS/MS nº 344/98).

Vencimento: Assim como a AFE, a AE não possui um prazo de validade pré-determinado, mas exige revalidação periódica e comunicação de quaisquer alterações.
Licença de Operação (LO) Ambiental: Para estabelecimentos que geram algum tipo de impacto ambiental (ex: resíduos de saúde). Emitida por órgãos ambientais estaduais ou municipais.

Vencimento: Varia conforme o tipo de atividade e o órgão emissor, mas geralmente possui validade de 1 a 5 anos. A renovação deve ser solicitada antes do vencimento.
Plano de Gerenciamento de Resíduos de Serviços de Saúde (PGRSS): Documento obrigatório para todos os geradores de resíduos de serviços de saúde.

Vencimento: O PGRSS deve ser revisado anualmente ou sempre que houver alterações nas atividades do estabelecimento, conforme a RDC/ANVISA nº 222/2018 e a CONAMA nº 358/2005. Embora o plano em si não "vença", sua adequação e atualização são contínuas.
Certificado de Conformidade do Corpo de Bombeiros (AVCB ou CLCB): Atesta que o estabelecimento possui as condições de segurança contra incêndio e pânico.

Vencimento: A validade varia de 1 a 5 anos, dependendo do risco da edificação e da legislação estadual ou municipal.
Para Profissionais de Saúde
Registro Profissional (CRM, COREN, CRO, CRF, etc.): O registro no conselho de classe (Conselho Regional de Medicina, Enfermagem, Odontologia, Farmácia, etc.) não possui um prazo de validade, mas é válido enquanto o profissional estiver em dia com suas anuidades e sem impedimentos éticos ou disciplinares.

Vencimento: A anuidade do conselho deve ser paga anualmente. O não pagamento pode levar à suspensão ou cassação do registro.
Carteira de Identidade Profissional: Emitida pelos conselhos, este documento acompanha o registro.

Vencimento: Geralmente, possui validade de 5 a 10 anos, dependendo do conselho. A renovação implica na emissão de uma nova carteira.
Certificados de Cursos e Especializações: Embora os certificados em si não "vençam", em algumas áreas da saúde (especialmente as que envolvem tecnologias ou procedimentos em constante evolução), a atualização profissional contínua é fundamental e pode ser exigida por instituições empregadoras ou para a manutenção de certas qualificações.

Outros Documentos Relevantes
Atestados de Saúde Ocupacional (ASO): Documento que avalia a saúde do trabalhador em relação à sua função.

Vencimento: A validade do ASO varia conforme o tipo de exame (admissional, periódico, demissional, retorno ao trabalho, mudança de função) e o risco da atividade, conforme a NR-7 do Ministério do Trabalho. Exames periódicos podem ter validade de 6 meses a 2 anos.
Laudos e Licenças para Equipamentos Específicos: Alguns equipamentos utilizados em saúde (ex: aparelhos de raios-X, autoclaves) exigem laudos de calibração, certificações e licenças específicas, que possuem prazos de validade variados.

Vencimento: Anual ou bienal, dependendo do tipo de equipamento e da regulamentação.
Observações Importantes:
Legislação Local: É crucial consultar a legislação específica do seu município e estado, pois podem existir particularidades e exigências adicionais em relação aos prazos de validade.
Acompanhamento Constante: A legislação sanitária e de saúde está em constante atualização. É fundamental que os profissionais e estabelecimentos mantenham-se informados sobre novas normas e regulamentos.
Consequências da Não Conformidade: O descumprimento dos prazos e a falta de regularização dos documentos podem acarretar em multas, interdição do estabelecimento e outras sanções administrativas e legais.
Espero que esta lista detalhada seja útil para você! Se tiver alguma dúvida sobre um documento específico, me diga.

https://www.intodns.com/pontocomsistemas.com.br

Por favor atualiza os DNS no registro.br para:

ns1.dnsnix.com.br
ns2.dnsnix.com.br
ns3.dnsnix.com.br
ns4.dnsnix.com.br

https://99clinic.com/planos
https://www.amaissaude.com.br/sp/artigos/conteudo-medico-papel-do-teste-ergometrico-na-avaliacao-pre-operatoria-de-cirurgia-nao-cardiaca/
https://omarmejia.com.br/calculadora-de-riscos-cirurgicos/
https://novnc.com/info.html
https://www.tightvnc.com/download.php
https://professional.heart.org/en/guidelines-and-statements/prevent-calculator
https://www.misodor.com.br/RISCOPERIOPCARVASC.php
https://manager.madbuilder.com.br/post-open-10372
PROCESSOS
    CONTRATOS -> Gerar contrato, depois cadastra o contas a pagar ou a receber e emiti duplicata 
*----------------------------------------------------------------------------------------------------------------*
COM O FABRICIO
    1)TABELA DE PREÇO (class TabeladeprecoList extends TPage)
    ***Não esta gerando a tabela                              
    ***TESTAR - FEITO EM  19/11/2024

    2)PESSOA (class PessoaForm extends TWindow): 
    ***Cadastro de parceiros ao editar faz a validação que ja existe ( public  function onAddDetailPessoaPlanodesaudePessoa($param = null) ) o correto é validar somente na inlusão
    ***TESTAR - FEITO EM  02/12/2024
    
    3)MOVIMENTO DE CAIXA - NAO ESTA TRAZENDO SALDO INICIAL, TELA E RELATORIO 
        Relatorio de caixa
        	Não está trazendo o nome do caixa no relatorio
        	Se não informa o caixa, e clicar no relatorio traz a movimentação que estiver na data inicio e final
        	No list aparece a mensagem Tentativa de acesso à uma propriedade não exist quando se escolhe saldo anterior sim ou nao
        	Não obedece os filtros exemplo caixa 9 tem movimentação na impressão sai outra movimentação
    ****TESTAR
 
    4)CRIAR TABELA DIAS_SEMPROCEDIMENTO - ja esta criada
        ID              = Codigo automatico
        COR             = Vermelho / Amarelo ....
        DESCRICAO       = Paciente com mais de 60 dias sem consulta
        DIAS            = 60
    
        FAZER TRANSFORMAÇÃO - buscar na tabela DIAS_SEMPROCEDIMENTO a coluna dias para listar a cor na listagem de pessoas.
        Colocar filtro por dias na tela pessoaslist
    ****TESTAR
    
    5)RISCO CIRURGICO - cadastro dos riscos com problemas e Substituir o item 6 pelo erros nos relatorios com a model
    
    ***6)Ficar enviando notificação em quanto tiver contas a pagar em aberto ou a receber no dia, pode deixar para ser parametrizado exemplo a cada 1 hora
    
*************----------------------------------------------------------------------------------------------------*
VERIFICAR ESTES ITENS 
    1)AGENDAMENTO = criar validação para a data final se for maior emitir alerta
    
    2)VOUCHERS - Cadastro de paciente permitindo cadastrar sem parceria
    
    3)PROTOCOLO DE EXAME - Baixa total dos exames pelo cabeçalho e parcial pelos itens
    
    4)AUTORIZAÇÃO DE DESCONTO COM ERRO AO FECHAR O FORMULARIO FICA EM LOOP

    5)CAPTURAR TELA - https://html2canvas.hertzen.com/
    
    6)Fechamento diario mandar aviso para todos que estiver cadastrado
    
    7AUTORIZAÇÃO DE DESCONTO POR AGENDAMENTO - Verificar as cobranças
    
    8)EDITOR / LAUDO:
    ***Verificar os tipos
    ***Impressao da imagem
    ***Testar áudio
    ***Criar recurso de poder trazer os campos da tabelas
    ***Gerar assinado com o certificado A1 e mudar para buscar os dados do cadastro de pessoa
    ***Disponibilizar o acesso externo para que o paciente pegue o seu laudo, o laudo sera gerado no momento em que o paciente clicar no botão gerar onde sera assinado usando a assinatura no cadastro e caso tenha o certificado A1
    ***O laudo precisa sair uma assinatura na vertical ou acima do rodape, esta assinatura precisa ser é menor que assinatura normal.
    ***Ativar o recurso de transformar voz em texto para laudo e demais documentos do sistema.
    ***Problema com relatorio campo tipo

    9)AGENDAMENTO:
    ***Criação de agendamento em lote
    ***Quando duplicar o agendamento colocar no campo anterior o registro para poder saber qual foi gerado
    ***Bug ao cadastrar rapido de paciente e buscar a tabela de preço
    ***Agendamento disparar lembretes automatico e manual de confirmação de consulta se tiver como receber confirmação e gravar no sistema seria otimo
    ***Listar pacientes sem consulta a x dias
    
    10)MENSAGEM:
    ***Gerar para o grupo de diretoria contas a pagar / contas a receber a vencer ou vencido 
    ***Vencimento de produtos
    ***Vencimento diversos
    ***Trazer o nome do usuario na mensagem Aguarde...
    
    11)RESULTADO - Disponibilizar no portal

    12)PRODUTO - Kit de produto - Ao fazer algum procedimento baixar do estoque o kit

    13)AGENDA DO MEDICO - Foto do medico na tela

    14)RISCO Cirúrgico

    15)INTEGRAR:
    ***Sistema de proteção
    ***Dicom - (https://apps.nextcloud.com/apps/dicomviewer)
    ***Videochamada (https://apps.nextcloud.com/apps/spreedme  /  https://apps.nextcloud.com/apps/webex_picker)
    ***Sistema de atendimento (Painel de senha)
    ***Whatsapp 
        **Aniversario disparar mensagem automatico e manual
        **Fechamento do dia -  secretaria faz um resumo das operações do dia e precisa disparar isso para os contatos cadastrados, depois que cadastrar clicar em enviar
        **A interação do zap por dentro do sistema
        **Modulo para configurar as opções do zap

    16)EXCLUSÃO
    ***Verificar integridade do registro na exclusão

    17)Instalador do GESTORWEB com algumas Ferramentas    
*************----------------------------------------------------------------------------------------------------*
    APP DE AGENDAMENTO:
        1)PACIENTE PODE SE CADASTRAR NO APP ONDE SERA GERADO O USUARIO E SENHA PARA PODER UTILIZAR O APP DEPOIS DE SINCRONIZADO
            USUARIO E SENHA DEVE SER GRAVADO NO GESTORWEB
        
        2)ESCOLHER O MEDICO:
        	FILTRAR PELA ESPECIALIDADE
        
        3)HORARIO:
        	BUSCAR DISPONIVEL DO GESTOR WEB - AVISANDO QUE PODE OCORRER DO HORARIO JA TENHA SIDO PRENCHIDO POR OUTRO PACIENTE E QUE O SISTEMA RETORNA O HORARIO VAGO MAIS PROXIMO DO ENVIADO
        
        4)STATUS: 
        	CONFIRMA / DESMARCA / NÃO COMPARECEU / CANCELAR AGENDAMENTO
        
        5)O APP DEVE SINCRONIZAR COM O GESTORWEB

*************----------------------------------------------------------------------------------------------------*

*----------------------------------------------------------------------------------------------------------------*
Estratégia e soluções de crescimento para o seu negócio
Descomplique a gestão da sua clínica!
O Clinux é a ferramenta de gestão de clínicas com melhor custo-benefício do mercado, que permite controlar todo o processo interno, desde os agendamentos até à conciliação do faturamento
*----------------------------------------------------------------------------------------------------------------*
ADICIONAR BORDA VERMELHA NO CAMPO OBRIGATÓRIO

https://botman.io/
https://www.chatwoot.com/
https://github.com/ticketz-oss/ticketz
https://github.com/EvolutionAPI/evolution-api
https://manager.madbuilder.com.br/post-open-9777
https://phpbrasil.com/scripts?page=3&s=date&author=&q=&tipo=&tags=data-hora
https://manager.madbuilder.com.br/post-open-12360

ATESTADO FISIOTERAPÊUTICO

Atesto para os devidos fins que o(a) Sr(a). (nome), inscrito(a) no CPF sob o nº (informar), esteve em tratamento na (nome da clínica), sob meus cuidados, no dia (data), das (horário) à (horário), em tratamento para (nome da doença), tendo sido submetido à sessão composta por eletroterapia analgésica e massagem  terapêutica (substitua pelos procedimentos realizados).

(município) - (UF), (dia) de (mês) de (ano).

(assinatura e carimbo)
(nome do(a) profissional)
Fisioterapeuta
(número de registro)

Adicionamos uma nova propriedade dentro do application.ini na sessão [general] chamada application_version dessa forma será possível obter de lá.

Para obter o valor você faz assim:

$ini = AdiantiApplicationConfig::get();
$application_version = $ini['general']['application_version'];
Se o projeto estiver em produção precisa atualizar o arquivo app/config/application.ini

"{application_version}"


https://telemedicinamorsch.com.br/blog/protocolo-de-risco-cirurgico#:~:text=Risco%20cir%C3%BArgico%20pode%20ser%20definido,ou%20n%C3%A3o%2C%20o%20procedimento%20cir%C3%BArgico.
Caracteristicas:

    * Avaliacoes utilizando ASA (American Society of Anesthesiologists ), Goldman, Detsky, Larsen e Lee
    * Avaliacao de risco de complicacao pulmonar (Torrington & Henderson)
    * Calcula o  IMC ( Indice de Massa Corporea)
    * Avalia risco de sangramento devido a medicamentos
    * Avalia risco de trombose venosa profunda (2 metodos diferentes: Kakkar e  ACCP)
    * Imprime laudo completo sobre o paciente
    * Laudo pode ser customizado completamente, com imagens e textos. Para quem entende de  HTML e possivel editar a fonte, caso nao entenda, o editor embutido e autoexplicativo.
    * Apos pronto, o laudo pode novamente ser corrigido/personalizado/mudado antes da impressao

Cálculo do Risco Cirúrgico: Passo a Passo Detalhado e Fórmulas
O cálculo do risco cirúrgico é um processo crucial para estimar a probabilidade de um paciente desenvolver complicações durante ou após um procedimento cirúrgico. Essa avaliação pré-operatória detalhada auxilia na tomada de decisões importantes, como a escolha do tipo de anestesia, a necessidade de exames complementares e o planejamento de medidas profiláticas.

1. Etapas Essenciais:

O processo de avaliação do risco cirúrgico envolve diversas etapas interligadas:

a) Coleta de Dados:

Histórico Médico: Realize uma anamnese completa, incluindo doenças preexistentes, alergias, medicamentos em uso, cirurgias prévias, hábitos de vida (tabagismo, etilismo) e histórico familiar.
Exame Físico: Examine os sistemas cardiovascular, pulmonar, neurológico e outros relevantes, buscando sinais de doenças crônicas ou agudas.
Exames Complementares: Solicite exames laboratoriais e de imagem, de acordo com as necessidades individuais do paciente e a natureza da cirurgia.
b) Identificação de Fatores de Risco:

Com base nas informações coletadas, identifique os fatores de risco que podem influenciar o desfecho cirúrgico:

Idade: Pacientes idosos geralmente apresentam maior risco de complicações.
Estado Geral de Saúde: Doenças crônicas como diabetes, hipertensão arterial e cardiopatias aumentam o risco.
Condição Física: Obesidade, desnutrição e baixa reserva funcional podem comprometer a recuperação.
Tipo de Cirurgia: Cirurgias de grande porte, de longa duração ou em áreas de difícil acesso geralmente apresentam maior risco.
Comorbidades: A presença de doenças concomitantes, como insuficiência renal ou doença pulmonar obstrutiva crônica, eleva o risco.
c) Classificação do Risco:

Utilize ferramentas validadas para classificar o risco cirúrgico do paciente. As opções mais comuns são:

Classificação ASA (American Society of Anesthesiologists): Essa classificação física classifica os pacientes em seis categorias, de I (paciente saudável) a VI (paciente moribundo).
Índice de Risco Cardíaco de Goldman: Destinado à avaliação do risco de complicações cardíacas em cirurgias não cardíacas.
Índice de Risco Pulmonar de Gupta: Avalia o risco de complicações pulmonares pós-operatórias.
Escore Multivariável de Avaliação Pré-Operatória (EMAPO): Método brasileiro abrangente que considera 27 variáveis para estimar o risco cirúrgico geral.
Cada ferramenta possui suas variáveis e métodos de cálculo específicos. A escolha da ferramenta ideal dependerá da familiaridade do profissional, da complexidade da cirurgia e das características do paciente.

d) Interpretação dos Resultados:

Após a classificação do risco, interprete os resultados à luz das características individuais do paciente e da cirurgia proposta.

Risco Baixo: Pacientes com baixo risco geralmente têm boa recuperação e baixa probabilidade de complicações.
Risco Intermediário: Nesses casos, é necessária uma avaliação mais criteriosa e a implementação de medidas profiláticas para reduzir o risco.
Risco Alto: Pacientes com alto risco podem necessitar de avaliação por especialistas em áreas específicas, adiamento da cirurgia ou adoção de medidas mais agressivas para minimizar os riscos.
e) Comunicação e Planejamento:

Comunique os resultados da avaliação ao paciente, equipe cirúrgica e demais profissionais envolvidos no cuidado. Utilize essa informação para:

Orientar o paciente: Esclareça os riscos e benefícios da cirurgia, as medidas de preparo necessárias e as expectativas para a recuperação.
Definir o plano anestésico: Escolha a técnica anestésica mais adequada ao perfil de risco do paciente e à natureza da cirurgia.
Implementar medidas profiláticas: Adote medidas para prevenir complicações, como trombose venosa profunda, infecções e disfunção pulmonar.
Otimizar o manejo perioperatório: Planeje o cuidado multiprofissional durante o período perioperatório, incluindo monitoramento, suporte ventilatório e terapia medicamentosa.
2. Fórmulas Utilizadas:

As fórmulas específicas variam de acordo com a ferramenta de classificação de risco utilizada. Abaixo, detalhamos algumas das mais comuns:

a) Classificação ASA:

A classificação ASA não possui uma fórmula matemática, mas sim uma categorização baseada em critérios clínicos.

b) Índice de Risco Cardíaco de Goldman:

O índice de Goldman considera 9 variáveis:


Programas para Cálculo de Risco Cirúrgico: Ferramentas Digitais para uma Avaliação Precisa
Os programas de cálculo de risco cirúrgico se consolidam como ferramentas valiosas para auxiliar profissionais da saúde na avaliação pré-operatória de pacientes. Através da automação de cálculos e da integração com prontuários eletrônicos, esses programas otimizam o processo de avaliação, aumentam a precisão dos resultados e facilitam a comunicação entre os profissionais envolvidos no cuidado do paciente.

1. Benefícios da Implementação:

A utilização de programas para o cálculo de risco cirúrgico oferece diversos benefícios:

Agilidade e Praticidade: Automatizam cálculos complexos, liberando tempo para que o profissional se concentre na avaliação clínica individualizada do paciente.
Maior Precisão: Reduzem a margem de erro humano nos cálculos, garantindo resultados mais confiáveis.
Padronização da Avaliação: Promovem a padronização dos métodos de avaliação, assegurando maior homogeneidade nos resultados entre diferentes profissionais e instituições.
Integração com Prontuários Eletrônicos: Facilitam o acesso às informações do paciente e a comunicação entre os profissionais, otimizando o fluxo de trabalho e a qualidade da assistência.
Tomada de Decisões Mais Informada: Auxiliam na tomada de decisões mais assertivas sobre o tipo de anestesia, a necessidade de exames complementares e o planejamento de medidas profiláticas.
Melhora na Segurança do Paciente: Contribuem para a redução do risco de complicações cirúrgicas e para a melhora dos resultados em saúde.

2. Opções Disponíveis no Mercado:

Diversos programas de cálculo de risco cirúrgico estão disponíveis no mercado, cada um com suas características e funcionalidades específicas. Alguns dos mais utilizados no Brasil são:

Risco Cirúrgico MED:
Desenvolve pela empresa Medware, o Risco Cirúrgico MED se destaca pela sua interface amigável e pela abrangência das variáveis consideradas no cálculo do risco. O programa permite a utilização de diversas classificações de risco, como ASA, Goldman, EMAPO e Detsky, além de oferecer recursos para a geração de laudos personalizados e a integração com prontuários eletrônicos.
Abre em uma nova janela
riscocirurgico.com.br
Risco Cirúrgico MED software
EasyRisk:
Desenvolvido pela Sociedade Brasileira de Anestesiologia (SBA), o EasyRisk é uma ferramenta gratuita e de fácil utilização que se baseia na Classificação ASA para o cálculo do risco cirúrgico. O programa oferece recursos para a geração de laudos e para a impressão da classificação de risco.
Abre em uma nova janela
www.marinelink.com
EasyRisk software
Protocolo Eletrônico de Risco Cirúrgico (PERC):
Criado pela Sociedade Brasileira de Cirurgia Plástica (SBCP), o PERC é um programa específico para o cálculo do risco cirúrgico em pacientes submetidos a cirurgias plásticas. O programa considera as características específicas dessa especialidade e oferece recursos para a avaliação do risco individualizado de cada paciente.
Anestesia Fácil:
O Anestesia Fácil é um software completo para gestão da anestesia, que inclui um módulo para o cálculo do risco cirúrgico. O programa utiliza diversas classificações de risco e oferece recursos para a geração de laudos, a impressão da classificação de risco e a integração com prontuários eletrônicos.
Abre em uma nova janela
www.tissmed.com.br
Anestesia Fácil software

3. Fatores a Considerar na Escolha:

A escolha do programa ideal para o cálculo de risco cirúrgico deve considerar diversos fatores, como:

Funcionalidades: Avalie as funcionalidades oferecidas pelo programa, como as classificações de risco disponíveis, os recursos para a geração de laudos e a possibilidade de integração com prontuários eletrônicos.
Facilidade de Uso: Opte por um programa com interface amigável e intuitiva, que seja fácil de aprender e utilizar no dia adia.
Confiabilidade: Verifique a reputação do desenvolvedor do programa e a qualidade dos dados científicos utilizados nos cálculos.
Suporte Técnico: Avalie a qualidade do suporte técnico oferecido pelo desenvolvedor do programa, em caso de dúvidas ou problemas.
Custo: Compare os preços dos diferentes programas disponíveis e escolha a opção que melhor se encaixa no orçamento da sua instituição.

4. Recomendações para Implementação:

Para uma implementação bem-sucedida do programa de cálculo de risco cirúrgico em sua instituição, siga estas recomendações:

Defina um protocolo: Estabeleça um protocolo para a utilização do programa, incluindo quais profissionais

Quando o ensaio e leitura são lançados no sistema, não tem como ser cancelado, o que recomenda-se é que ao fazer o processo de aferição, seja feito uma conferencia do disco ou da fita antes de enviar ao INMETRO, caso tenha alguma duvida das informações geradas no disco ou fita, neste caso pode solicitar o cancelamento do ensaio, para que seja feito novamente.

burnout-teste

Em quanto tempo depois de acordar você fuma o primeiro cigarro?
Dentro de 5 minutos
6-30 minutos
31-60 minutos
Depois de 60 minutos

Você acha difícil deixar de fumar em lugares onde é proibido (por exemplo, na igreja, no cinema, em bibliotecas, etc.)
Sim
Não

Que cigarro você mais sofreria em deixar?
O primeiro da manhã
Qualquer um

Quantos cigarros você fuma por dia?
31 ou mais
21-30
11-20
10 ou menos

Você fuma mais durante as primeiras horas após acordar do que durante o resto do dia?
Sim
Não

Você fuma mesmo estando tão doente que precise ficar de cama quase todo o dia?
Sim
Não

Resultado: (0)

Avaliação do resultado
Dependência (soma dos pontos):

0-2: muito baixa
3-4: baixa
5: média
6-7: elevada
8-10: muito elevada

IMC IDOSO:
Na terceira idade podem acontecer diversas alterações na composição corporal, como a compressão vertebral e a redução natural do tônus muscular. Para pessoas acima dos 60 anos, o Ministério da Saúde do Brasil adota o padrão de Lipschitz, indicado na tabela de referência abaixo.<br>
IMC INFANTIL:
Para crianças e adolescentes o ideal é a avaliação médica individualizada, pois o cálculo geral do IMC pode não ser eficiente, afinal não leva em consideração a idade ou o gênero.<br>
<br>
O acompanhamento médico nessa fase da vida é essencial para evitar problemas de saúde na vida adulta.<br>
IMC GESTANTE:
Para calcular o peso ideal da gestante, pode ser usada a mesma calculadora de IMC. O que muda é a avaliação dos resultados, geralmente feita por um especialista, levando em consideração a idade gestacional e as condições de saúde da futura mamãe.<br>
<br>
A cada semana, o cálculo é feito pelo médico para avaliar o estado nutricional. É esperado que a mulher ganhe de 8 a 12 quilos durante a gestação, portanto o Índice de Massa Corporal varia durante as fases da gravidez.<br>
<br>
A tabela abaixo pode ser usada de referência, mas somente um profissional de saúde pode confirmar se sua gestação está sendo saudável ou não, além de fazer as recomendações alimentares de acordo com o seu estilo de vida.<br>
IMC ATLETAS:
Pessoas que praticam atividades físicas contam com uma composição corporal mais robusta, com mais massa muscular e maior densidade óssea, o que pode indicar sobrepeso na calculadora de IMC, mesmo se tratando de alguém saudável. Por isso, o peso ideal desse grupo deve ser indicado por um profissional de saúde especializado, como médicos ou nutricionistas.<br>


Qual é a validade de um pedido médico e encaminhamento?
A validade desses dois documentos é de 60 dias, independentemente se foi feito pelo Time de Saúde ou não.

Ponto.Com Sistemas
Soluções inovadoras para a gestão da sua clínica médica.
Tipo de site:
Fabricante de software de informática
Aplicativos do site
Agendamentos
Metas:
Desenvolver sistemas eficientes e inovadores, Atender às diversas necessidades dos clientes, Oferecer soluções de software
Público-alvo:
Gestores de clínicas médicas que buscam soluções tecnológicas para otimizar a gestão de seus negócios., Profissionais de saúde que desejam informatizar seus consultórios e melhorar a eficiência de seus processos., Empresas de saúde que buscam soluções personalizadas para atender às suas necessidades específicas.
Tom de voz:
Profissional e experiente

Dra Carita - seria esta CARITA AGUIAR carita.aguiar
Dra Flávia Thiago - seria esta Flávia Christina Tiago Arruda flavia.arruda
Dra Marina - seria esta Marina de Souza Fleury marina.fleury

O estatuto dispõe sobre a proteção integral à criança e ao adolescente em diversos setores. Segundo o ECA, é considerado criança quem tem até 12 anos incompletos. Já entre 12 e 18 anos são adolescentes. A lei define que esta faixa etária têm direito à vida e à saúde; à liberdade, ao respeito e à dignidade; à convivência familiar e comunitária; e do direito à guarda, à tutela e à adoção.
Os menores de dezesseis anos serão sempre representados; os maiores de dezesseis e menores de dezoito serão assistidos.

*----------------------------------------------------------
Termos e Condições do Programa de Vouchers
O programa de vouchers é aplicável exclusivamente a compras efetuadas na loja online d’A Vida Portuguesa, no sítio da internet www.avidaportuguesa.com.
O programa de vouchers é aplicável a cada conta-cliente registada na loja online d’A Vida Portuguesa, no sítio da internet www.avidaportuguesa.com.
O programa de vouchers consiste na emissão de um crédito, designado por “voucher”, de €7,50 (sete euros e cinquenta cêntimos) por cada €75,00 (setenta e cinco euros) efetivamente despendidos pelo cliente em produtos comprados na loja online, excluindo portes de envio, despesas de transporte, de entrega ou outras despesas aplicáveis.
O valor dos produtos adquiridos que ainda não tenha sido utilizado na emissão de um voucher é acumulado para a emissão do voucher seguinte.
O valor dos produtos adquiridos que ainda não tenha sido utilizado na emissão de um voucher é subtraído ao valor acumulado nos termos do artigo anterior, quando tenham decorrido seis meses sobre a aquisição desses produtos.
Cada voucher tem uma validade de 30 dias a contar da data da compra que originou a sua emissão, caducando se não for utilizado nesse prazo.
Apenas pode ser utilizado um voucher por cada encomenda na loja online.
Os vouchers apenas podem ser utilizados em encomendas de produtos que, no seu conjunto, tenham valor igual ou superior a €25,00 (vinte e cinco euros), excluindo portes de envio, despesas de transporte, de entrega ou outras despesas aplicáveis.
No caso de haver devolução ou troca de produtos, o valor devolvido ao cliente será descontado ao valor acumulado e os vouchers emitidos em excesso serão cancelados.
Os vouchers serão cancelados sempre que seja detetada a prática de um crime, fraude, utilização indevida ou uso comercial.
Para execução do programa de vouchers, a Passos em Volta, Unipessoal, Lda. procederá ao tratamento dos dados pessoais dos clientes associados à conta-cliente, bem como os dados relativos às transações por estes efetuadas na loja online, garantindo a sua confidencialidade. Os clientes podem exercer os seus direitos de acesso, de retificação, de apagamento, de limitação do tratamento, de portabilidade e de oposição ao tratamento dos seus dados pessoais através de (lojaonline@avidaportuguesa.com).
As presentes condições podem ser alteradas a qualquer momento, sendo tal facto comunicado ao cliente com uma antecedência mínima de 15 (quinze) dias na sua área reservada da loja online d’A Vida Portuguesa, no sítio da internet www.avidaportuguesa.com.
*----------------------------------------------------------------------------------------------------------------*
Regras gerais

Cada voucher possui um código unico, que será utilizado pelo paciente, descrito no campo DADOS DO PACIENTE;
Só é permitida a utilização de um voucher por agendamento;
O voucher tem uma validade especifica e esta discriminada, apos a contar da data de emissão;
O valor correspondente do voucher jamais será revertido em dinheiro;
Em caso de cancelamento do agendamento, o valor a ser reembolsado será o mesmo do pagamento efetuado, já incluindo o desconto.
O não comparecimento implicará na perda do voucher;
Ação válida por tempo limitado, podendo ser encerrada sem aviso prévio;
O Voucher tem validade apenas para a especialidade mencionada;
É PESSOAL E INTRANSFERÍVEL.
O VOUCHER NÃO PODE SER COMERCIALIZADO.
O VOUCHER NÃO POSSUI CUSTOS FINANCEIROS E NEM TAXAS ADICIONAIS.

Condições Gerais
O cheque presente é um cheque que serve como meio de pagamento válido em qualquer Clínica Médis e pode ser utilizado em qualquer tratamento. O cheque presente está disponível para venda em qualquer Clínica Médis e pode ser emitido com um valor à escolha do cliente. A venda do cheque presente está sujeita à disponibilidade dos mesmos na Clínica Médis.

No momento da venda, o cliente deve fornecer o nome e o NIF do cliente a quem se destina o cheque presente. O cheque presente é válido por um ano desde a data da sua compra, pelo que a marcação e realização da consulta têm de ocorrer dentro desse espaço temporal, sob pena de perda dos direitos conferidos pelo voucher.

A data de emissão é adicionada ao cheque presente pelo gestor de paciente que o emitir, correspondendo à data da compra que inicia a contagem do prazo.

No dia da consulta, o paciente deve apresentar o cheque presente como meio de pagamento ao gestor de paciente de modo a poder usufruir do mesmo. O paciente deve ainda fazer-se acompanhar de documentação que permita comprovar o respetivo NIF.

O voucher oferta é pessoal e intransmissível. Este voucher pode ser aplicado sobre o valor a cargo do cliente no âmbito do seu plano de saúde Médis, mediante apresentação de cartão de seguro Médis válido.

Os clientes que não cumprirem estes requisitos podem utilizar o voucher aplicado a preços de tabela particular. 

Caso o valor total dos tratamentos seja superior ao indicado no voucher oferta, a diferença deverá ser complementada pelo paciente, com qualquer outro meio de pagamento aceite na Clínica Médis.

No caso de não-utilização, perda, roubo ou destruição do voucher oferta, o cliente não poderá exigir qualquer compensação ou a utilização do mesmo.

O saldo do voucher oferta não é redimível por dinheiro, e não é acumulável com outras ofertas em vigor.
*---------------
CONDIÇÕES DE UTILIZAÇÃO:

Este código de voucher só pode ser utilizado em compras efetuadas diretamente ao Grupo LIDEL, em www.lidel.pt;
O voucher é válido até à data acima indicada. Uma vez ultrapassada a data de validade, o seu detentor não poderá utilizá-lo, nem reclamar o respetivo desconto;
O código do voucher terá de ser inserido no “carrinho de compras” no website da Lidel, em www.lidel.pt e atribui um desconto de 20% nos livros que já não se encontrem ao abrigo da Lei do Preço Fixo*, das áreas indicadas;
Este voucher apenas pode ser usado na compra de livro (não inclui eBooks);
O voucher não acumula com outras promoções em vigor, não é reembolsável nem pode ser trocado por dinheiro;
A utilização deste voucher pressupõe o conhecimento e aceitação das condições de utilização.
*---------------
TERMOS E CONDIÇÕES 
O programa de vouchers é aplicável exclusivamente a compras efetuadas na loja online 
Em caso de litígio de consumo, definido nos termos do disposto na Lei n.º 144/2015, de 8 de Setembro, o consumidor pode recorrer à entidade de resolução alternativa de litígios de consumo competente. Sem prejuízo do disposto na legislação, nos estatutos e nos regulamentos a que as entidades de resolução alternativa de litígios de consumo se encontram vinculadas, o consumidor pode optar pela plataforma europeia de resolução de litígios em linha disponível em https://webgate.ec.europa.eu/odr, pela entidade de resolução alternativa de litígios de consumo do local do seu domicílio ou pela entidade de resolução alternativa de competência especializada, caso exista para o sector em questão. Poderá consultar a lista actualizada de todas as entidades de resolução alternativa de consumo disponíveis em www.consumidor.pt.
Caso não exista(m) entidade(s) de resolução alternativa de litígios nos termos do disposto no número anterior ou a(s) existente(s) não se considere(m) competente(s) em razão do valor do litígio, o consumidor pode recorrer ao Centro Nacional de Informação e Arbitragem de Conflitos de Consumo, sito em Lisboa, com o endereço electrónico: cniacc@unl.pt e disponível na página www.arbitragem-deconsumo.org.
*----------------------------------------------------------------------------------------------------------------*
Condições  

O voucher tem validade de até 50 dias a partir da data de compra, sendo possível verificar o prazo de validade diretamente no voucher.  

O voucher não poderá ser convertido em dinheiro e exige marcação prévia por email (geral@sclinica.pt), telefone (252 961 638), ou presencialmente na S.Clínica.  

As reservas e marcações dos serviços contemplados pelo voucher estão sujeitas à disponibilidade de agenda da S.Clínica.  

Estamos dispostos para esclarecer quaisquer dúvidas ou fornecer informações adicionais. Os clientes podem entrar em contacto connosco através dos diversos meios disponíveis no nosso site. 

No dia agendado para a consulta/tratamento, é obrigatório que o portador do Voucher o apresente como forma de pagamento na receção. 

Para cancelamentos ou alterações nas marcações, o presenteado com o nosso voucher pode realizar estas mudanças diretamente na S.Clínica, por via telefone ou por email geral@sclinica.pt. 
*----------------------------------------------------------------------------------------------------------------*
Cada voucher possui um identificador e validade único, que será utilizado pelo beneficiário;
A utilização do voucher, só pode ser utilizada para a especialidade descrita e estão sujeitas à disponibilidade de data e hora;
O valor correspondente do voucher jamais será revertido em dinheiro;
O voucher é pessoal e intransferível.
O voucher não pode ser comercializado.
O voucher não possui custos financeiros e nem taxas adicionais.
O voucher não acumula com outras promoções em vigor, não é reembolsável nem pode ser trocado por dinheiro;

OID do registro profissional do médico;
OID da UF de registro profissional;
OID do registro profissional do farmacêutico;
OID da UF de registro profissional;
OID do documento (prescrição, atestado, solicitação de exames, relatório médico).

2.16.76.1.4.2.2		Conselho Federal de Medicina
2.16.76.1.4.2.2.1	Número de registro do profissional
2.16.76.1.4.2.2.2	UF de registro profissional
2.16.76.1.4.2.2.3	Especialidade
2.16.76.1.4.2.3		Conselho Federal de Farmácia
2.16.76.1.4.2.3.1	Número de registro do farmacêutico
2.16.76.1.4.2.3.2	UF de registro do farmacêutico
2.16.76.1.4.2.3.3	Especialidade
2.16.76.1.4.2.12	Conselho Federal de Odontologia
2.16.76.1.4.2.12.1	Número de registro do dentista
2.16.76.1.4.2.12.2	UF de registro do dentista
2.16.76.1.4.2.12.3	Especialidade


2.16.76.1.12.1		Documentos Digitais da Saúde
2.16.76.1.12.1.1	Prescrição de medicamento
2.16.76.1.12.1.2	Atestado médico
2.16.76.1.12.1.3	Solicitação de exame
2.16.76.1.12.1.4	Laudo laboratorial
2.16.76.1.12.1.5	Sumária de alta
2.16.76.1.12.1.6	Registro de atendimento clínico
2.16.76.1.12.1.7	Dispensação de medicamento
2.16.76.1.12.1.8	Vacinação
2.16.76.1.12.1.11	Relatório Médico


Clínica Psique
Rua Ermeti Simonetti, nº 84 - Setor Central, Anápolis - GO, 75040-450
Na lateral do antigo colégio objetivo
(62) 99462-3721

Quem somos:
Uma equipe multidisciplinar formada por profissionais da área da saúde, que acredita que o atendimento em conjunto obtém os melhores resultados. 

Missão:
Acolher, oferecer atendimento humanizado e serviços de qualidade ao paciente, familiares, colaboradores e profissionais e preservando sua individualidade.

Visão:
Tornar-se referência no segmento de saúde na região e ser reconhecida pela sociedade e parceiros, em excelência nas especialidades e serviços médicos.

Valores:
Comprometimento, simplicidade, ética, credibilidade, confiança, inovação e excelência

Algumas de nossas especialidades:
Cardiologia, Psicologia, Fisioterapia,

Alguns de nossos exames:
Exame de Holter, MAPA, ECG, Teste ergométrico e Laboratorial.

Somos uma equipe multidisciplinar formada por profissionais da área da saúde, que acredita que o atendimento em conjunto obtém os melhores resultados.

Algumas de nossas especialidades e exames - Cardiologia, Psicologia, Fisioterapia, Exames de Holter, MAPA, ECG, Teste ergométrico e Laboratorial.

Códigos de parceiros

Condições e validade de acordo com a campanha de marketing.
*----------------------------------------------------------------------------------------------------------------*

Exames laboratorial
Avaliação psicossocial
Cardiologia clínica
Eletrocardiograma - ECG
Fisioterapia
Holter
Mapa
Médico da família
Psicologia clínica
Teste ergométrico
Avaliação porte de arma

Licença médica de 2 dias:
Data da consulta médica: 8 de julho de 2023 (sexta-feira)
Justificativa: Atestado médico para licença de 2 dias.
Contagem dos dias de licença:
Dia 1: 9 de julho de 2023 (sábado)
Dia 2: 10 de julho de 2023 (domingo)
Retorno ao trabalho: 11 de julho de 2023 (segunda-feira)
Licença médica de 3 dias:
Data da consulta médica: 8 de julho de 2023 (sexta-feira)
Justificativa: Atestado médico para licença de 3 dias.
Contagem dos dias de licença:
Dia 1: 8 de julho de 2023 (sexta-feira)
Dia 2: 9 de julho de 2023 (sábado)
Dia 3: 10 de julho de 2023 (domingo)
Retorno ao trabalho: 11 de julho de 2023 (segunda-feira)
Licença médica de 5 dias:
Data da consulta médica: 8 de julho de 2023 (sexta-feira)
Justificativa: Atestado médico para licença de 5 dias.
Contagem dos dias de licença:
Dia 1: 8 de julho de 2023 (sexta-feira)
Dia 2: 9 de julho de 2023 (sábado)
Dia 3: 10 de julho de 2023 (domingo)
Dia 4: 11 de julho de 2023 (segunda-feira)
Dia 5: 12 de julho de 2023 (terça-feira)
Retorno ao trabalho: 13 de julho de 2023 (quarta-feira)
Licença médica de 7 dias:
Data da consulta médica: 8 de julho de 2023 (sexta-feira)
Justificativa: Atestado médico para licença de 7 dias.
Contagem dos dias de licença:
Dia 1: 8 de julho de 2023 (sexta-feira)
Dia 2: 9 de julho de 2023 (sábado)
Dia 3: 10 de julho de 2023 (domingo)
Dia 4: 11 de julho de 2023 (segunda-feira)
Dia 5: 12 de julho de 2023 (terça-feira)
Dia 6: 13 de julho de 2023 (quarta-feira)
Dia 7: 14 de julho de 2023 (quinta-feira)
Retorno ao trabalho: 15 de julho de 2023 (sexta-feira)

*----------------------------------------------------------------------------------------------------------------*
Orientações necessárias:
  O uso de biotina e suplementos alimentares que contenham biotina devem ser suspensos 3 dias antes da coleta.

Jejum:
  Para todas as idades jejum mínimo necessário de 4 horas.

Medicamento:
  Anotar os medicamento(s) do (s)último(s) 7 dias(s).

Aldosterona - urina
Orientações necessárias:
I - Material
- Este exame é realizado em urina colhida durante 24 horas.

II - Critérios de realização
- O cliente deve retirar, no Fleury, os frascos adequados para a coleta do material e a folha de instruções. É obrigatório a apresentação do pedido médico na retirada dos frascos.
- Nas 48 horas que antecedem o exame, o indivíduo não pode receber contraste radiológico.
- Para mulheres, o ideal é não fazer o exame durante a menstruação. Da mesma forma, não devem ser usados cremes/óvulos vaginais durante as 24 horas de coleta da urina.

III - Prazo para entrega do material
- A amostra precisa ser entregue no Fleury até 48 horas após o término da coleta.

Dias de Medicamento:
IMPORTANTE anotar os medicamentos dos últimos 30 dias.


Aldosterona - soro
Orientações necessárias:
- A coleta deve ser feita preferencialmente pela manhã, entre 7 e 10 horas, ou conforme solicitação médica.
- Se houver instruções especiais do médico para a realização de coleta do exame em pé, o cliente precisa permanecer duas horas nessa posição (parado ou andando) antes do sangue ser colhido.
- Se for solicitado exame após repouso, o cliente deve permanecer 30 minutos sentado antes da coleta.
- Caso não haja especificação de deambulação ou repouso na solicitação médica, o exame deve ser realizado sem preparo.

Dias de Medicamento:
IMPORTANTE anotar os medicamentos dos últimos 30 dias.  
*----------------------------------------------------------------------------------------------------------------*

TERMO DE CONSENTIMENTO LIVRE E ESCLARECIDO
O QUE É UM TERMO DE CONSENTIMENTO? É um documento explicativo para a segurança jurídica do profissional da saúde, pois tem o intuito de comprovar o esclarecimento pelo médico ao paciente de todas as informações envolvidas em determinado procedimento/tratamento.
PARA QUE SERVE UM TERMO DE CONSENTIMENTO? Por meio desse documento o paciente autoriza e declara ciência de todos os riscos envolvidos no procedimento a ser realizado, manifestando-se de forma livre e consciente.

DECLARAÇÃO MÉDICA
O QUE É UMA DECLARAÇÃO MÉDICA? É um documento informativo que, diferentemente do atestado médico, apenas informa determinada situação, como por exemplo: o comparecimento ao consultório para uma consulta ou a realização de algum exame.

ATESTADO MÉDICO
O QUE É UM ATESTADO MÉDICO? É o documento utilizado pelos profissionais da saúde para confirmar o estado de saúde do paciente.
PARA QUE SERVE UM ATESTADO MÉDICO? Pode ser utilizado para atestar a necessidade de afastamento nas suas atividades cotidianas, aptidão ou inaptidão para praticar atividades físicas e também para informar algum estado de saúde específico de longo prazo, como por exemplo: gravidez, enfermidade ou deficiência.

• Atestado Médico de Afastamento: Documento simplificado para afastamento temporário.
• Atestado de Acompanhamento: Confirma a presença de um acompanhante em consulta ou procedimento.
• Declaração de Comparecimento: Emitida pelo setor administrativo de saúde sem recomendação de afastamento do trabalho.
• Atestado de Saúde: Afirma a condição de saúde física e mental do paciente.
• Atestado de Saúde Ocupacional (ASO): Define a aptidão ou inaptidão do trabalhador para atividades laborativas.
• Declaração de Óbito: Documento com valor médico-legal e sanitário.
• Relatório Médico Circunstanciado: Resumo evolutivo do quadro clínico do paciente.
• Relatório Médico Especializado: Para fins de perícia, discorrendo sobre a enfermidade e tratamentos do requerente.
• Parecer Técnico: Documento opinativo emitido por especialista.
• Laudo Médico-Pericial: Emitido por perito oficial.
• Laudo Médico: Conclusão sobre exame complementar realizado.
• Solicitação de Exames: Requisição de exames específicos.
• Resumo ou Sumário de Alta: Relatório clínico elaborado no momento da alta hospitalar.

Confirmado - quando o paciente confirmou a marcação;
Não confirmado - quando o paciente não respondeu se pode confirmar a marcação, ou se ele disse que não poderá comparecer no dia pré agendado;
Faltou - quando o paciente confirmar a marcação e não comparecer;
Cancelado - quando o paciente cancela a marcação;
Em espera - quando o paciente chega na clínica e está aguardando atendimento;
Desistiu - esse status aparece apenas quando a marcação está como "em espera", e serve para casos que o paciente demorou a ser atendido e não quis mais esperar.
Em atendimento - está dentro do consultório médico sendo atendido;
Atendimento finalizado - o profissional de saúde já liberou o paciente.

Importante! 

Após a marcação ser colocada como "em atendimento" ou "finalizado", por motivos de segurança não é possível voltar esses status. Então fique atento para que não sejam colocados esses status por engano, pois é irreversível. 

Confirmado
Aguardando atendimento
Cancelado
Atendido
Não compareceu


*----------------------------------------------------------------------------------------------------------------*
https://abeso.org.br/obesidade-e-sindrome-metabolica/calculadora-imc/
https://abran.org.br/calculadoras/imc-infantil
https://adiantiframework.com.br/forum/view_2041?filtragem-de-municipios-usando-o-componente-tdbmultisearch
https://adiantiframework.com.br/forum/view_2278?calculo-data-vencimento
https://apibrasil.com.br/
https://apigratis.com.br/blog/Como-Enviar-Mensagens-de-Texto-pelo-API-do-WhatsApp-Gratis#google_vignette
https://api-wa.me/
https://app.calc.med.br/index.pl?form=84560&act=printForm
https://app.durable.co/website-builder
https://apps.nextcloud.com/apps/dicomviewer
https://aps.bvs.br/apps/calculadoras/index.php?page=10
https://aps.bvs.br/apps/calculadoras/index.php?page=11
https://aps.bvs.br/apps/calculadoras/index.php?page=12
https://aps.bvs.br/apps/calculadoras/index.php?page=2
https://aps.bvs.br/apps/calculadoras/index.php?page=3
https://aps.bvs.br/apps/calculadoras/index.php?page=4
https://aps.bvs.br/apps/calculadoras/index.php?page=5
https://archive.org
https://avell.com.br/
https://bigbluebutton.org/
https://blog.bsoft.com.br/arrendamento-de-veiculos
https://blog.devdata.com.br/obter-a-data-atual-por-extenso-pt_br-em-php/
https://blog.pat.com.br/exames-de-risco-cirurgico/
https://br.freepik.com/search?format=search&last_filter=page&last_value=7&page=7&query=medical+Logo&selection=1&type=vector#uuid=973d1541-623d-4f7f-9a49-8232a7dd221e
https://br.freepik.com/search?format=search&last_filter=page&last_value=80&page=80&query=clinical+Logo&selection=1&type=vector#uuid=ff8fc842-d099-4c5c-9421-f0da3a9d6bdd
https://br.taiwebs.com/windows/categories-programming-tool-8/11?lang=BR
https://br.taiwebs.com/windows/download-da-formmaker-1413.html
https://budibase.com/templates/
https://calculadora-risco.saude.go.gov.br/
https://calculadorasmedicas.com.br/calculadora/escala-comportamental-de-dor-bps
https://campana.com.br/exames/acth-plasma/
https://check-host.net
https://clincalc.com/
https://clude.com.br/surpreenda/
https://coderanch.com/c/frameworks
https://create.vista.com/pt/vectors/GW-Icon/
https://departamentos.cardiol.br/sbc-da/2015/CALCULADORAER2017/etapa1.html
https://devmedia.com.br/forum/quanto-cobrar-em-um-software/469580
https://downloadly.ir/
https://downloadly.ir/software/programming/tms-fnc-maps/
https://downloadly.ir/theme/themeforest-betheme-v21-5-1-html-responsive-multi-purpose-template/
https://drgabriel.med.br/testes-de-saude-mental/
https://eufacoprogramas.com/trabalho-freelancer-quanto-cobrar/
https://fixthephoto.com/pt/illustrator-online.html
https://framework.adianti.me/tutor/index.php?class=FormDynamicFilterView
https://framework.adianti.me/tutor/index.php?class=FormDynamicSessionCriteriaView
https://framework.adianti.me/tutor/index.php?class=FormHierarchicalComboView
https://framework.adianti.me/tutor/index.php?class=HomeView&method=onLoad&menu=Presentation&submenu=Reports
https://github.com/ayselafsar/dicomviewer
https://github.com/billbarsch/myzap
https://github.com/darcyclarke/Detect.js/blob/master/detect.js
https://github.com/Mrr66/php-notifique-me-whatsApp
https://github.com/nfephp-org
https://github.com/nitsan-technologies/ns_whatsapp
https://github.com/opentok/Opentok-PHP-SDK
https://github.com/orkestral/venom
https://github.com/picqer/php-barcode-generator
https://github.com/say2joe/jquery.disable-autocomplete
https://github.com/ultramsg/whatsapp-php-sdk
https://godotengine.org/
https://helpdeexames.com.br/visualizacao/hermes-pardini-mg/eletrocardiograma-convencional-ate-12-derivacoes
https://itopsiquiatria.com/testes-psiquiatricos/
https://itopsiquiatria.com/testes-psiquiatricos/
https://jami.net/
https://jitsi.org/
https://kiai.med.br/testes-psicologicos-online/
https://linhadecodigo.com.br/artigo/288/quanto-vale-o-seu-servico-aprend
https://linhasdecuidado.saude.gov.br/portal/todas-linhas
https://lucasfortaleza.com/test/teste-online-de-autismo-infantil-12-itens/
https://madbuilder.com.br/fullsamples/index.php?class=HabilidadesCheckForm&method=onShow
https://manager.madbuilder.com.br/post-open-10543
https://manager.madbuilder.com.br/post-open-5881
https://mattermost.com/pricing/
https://my.visme.co/templates/enlESDUya09TUCtodktneXpSRnYwdz09OjpLdWhyb2YvcW1kRHA0R2dVRndzaTNnPT0=/createProject#/branded/visme/all
https://nav.dasa.com.br/blog/calcular-imc
https://nextcloud.com
https://nulledpress.org/category/wordpress-themes/page/6/
https://oficinadepsicologia.com/testes-psicologicos/
https://oglobo.globo.com/economia/defesa-do-consumidor/leasing-nao-financiamento-pode-esconder-armadilhas-7428062
https://onlinephp.io/c/b35fa
https://ourcodeworld.com/articles/read/687/how-to-configure-a-header-and-footer-in-dompdf
https://owncloud.com
https://p2p.mirotalk.com/
https://packagist.org/explore/popular
https://penpot.app/
https://pesktop.com/
https://php.com.br/25?codigos-de-barras-incriveis-com-a-biblioteca-picqer
https://phpautocomplete.com/download/
https://portal.ifrn.edu.br/institucional/estudantes/saude/manual-de-boas-praticas-dos-servicos-de-saude/
https://prescricaoeletronica.cfm.org.br/faq_farmaceuticos/validade-das-receitas/#:~:text=Receita%20Simples%3A%2030%20dias%20(a,do%20dia%20da%20sua%20emiss%C3%A3o)
https://prescricaoeletronica.cfm.org.br/perguntas-frequentes/
https://products.containerize.com/form/
https://psycho-test.org/pt.html
https://pt.my-ekg.com/calculadoras-ecg/calculos-ecg.html
https://pt.stackoverflow.com/questions/266518/enviar-mensagem-para-whatsapp-via-site
https://pt.stackoverflow.com/questions/399149/key-aleat%C3%B3ria-durante-a-cria%C3%A7%C3%A3o-de-usu%C3%A1rio
https://pt.stackoverflow.com/questions/82134/como-fixar-footer-na-p%C3%A1gina-de-impress%C3%A3o
https://pt.stackoverflow.com/questions/8317/como-fazer-a-fun%C3%A7%C3%A3o-date-formatar-uma-data-em-portugu%C3%AAs
https://qrfy.com
https://qxmd.com/calculate/calculator_195/revised-cardiac-risk-index-lee-criteria#
https://risco.med.br/#funcionalidades
https://sanarmed.com/prescricao-medica-dados-modelos-caligrafia-e-carimbo/
https://satepsi.cfp.org.br/testesFavoraveis.cfm
https://sbcda.com.br/
https://screentogif.com
https://sistemas.cfm.org.br/prescricaoeletronica/
https://sites.google.com/view/drmontillamdc/capacitacion-pami-2023/calculadoras-medicas
https://telemedicinamorsch.com.br/blog/prescricao-medica
https://telemedicinamorsch.com.br/blog/teste-psicologico-online
https://telemedicinamorsch.com.br/blog/validade-receita-medica
https://tox.chat/
https://trueconf.com/pt-br/produtos/tcsf/servidor-trueconf-gratis.html
https://ultramsg.com/pt/home/articles/how-to-send-message-by-whatsapp-api-using-php.php
https://vdo.ninja/
https://videojs.com/advanced?video=disneys-oceans
https://web.ondoctor.app/signup
https://webcode.tools/css-generator
https://widoctor.com.br/2014/01/09/ecg-modulo-1/
https://wppconnect.io/pt-BR/swagger/wppconnect-server/
https://www.123test.com/pt/teste-vocacional/
https://www.apsivida.com/pol%C3%ADticadeprivacidade
https://www.bibliomed.com.br/calculadoras/periorisk/index.cfm
https://www.bibliomed.com.br/calculadoras/periorisk/index.cfm
https://www.bibliomed.com.br/calculadoras/scorefram/index.cfm
https://www.bibliomed.com.br/calculadoras/timiangina/index.cfm
https://www.bibliomed.com.br/calculadoras/timistemi/index.cfm
https://www.bosontreinamentos.com.br/php-programming/curso-de-php-operadores-logicos-bit-a-bit-e-relacionais/
https://www.calc.med.br/calculadoras/indice+de+Adiposidade+Corporal+IAC-F67IYIVC.html
https://www.calculoimc.com.br/imc-infantil/#google_vignette
https://www.clinicadamente.com/testes-psicologicos-de-autoavaliacao/
https://www.crmpr.org.br/Medicos-do-SUS-devem-prescrever-medicamentos-pelo-principio-ativo-11-48453.shtml
https://www.doctorview.com.br/suporte/
https://www.einstein.br/saudemental
https://www.fleury.com.br/busca/?busca=aldosterona
https://www.free-powerpoint-templates-design.com/free-powerpoint-templates-design/page/80/
https://gist.github.com/fadevbr/8e8cd4c96d02424c4d737ece6a334691
https://www.gov.br/ebserh/pt-br/hospitais-universitarios/regiao-nordeste/hulw-ufpb/acesso-a-informacao/gestao-documental/pop-procedimento-operacional-padrao
https://www.gov.br/ebserh/pt-br/hospitais-universitarios/regiao-sudeste/hc-uftm/documentos/procedimentos-e-rotinas-operacionais-padrao/pops
https://www.gov.br/inca/pt-br/assuntos/gestor-e-profissional-de-saude/programa-nacional-de-controle-do-tabagismo/teste-de-fargestrom
https://www.hiwellapp.com/pt-BR/testes/teste-de-depressao
https://www.homed.com.br/mascaras/nasal/mascara-nasal-inspira-silicone-homed
https://www.hospitalpuc-campinas.com.br/documentos-medicos/
https://www.idrlabs.com/pt/rorschach/teste.php      
https://www.linphone.org/products
https://www.lumiarsaude.com.br/mascara-nasal-mirage-fx
https://www.mccc.edu/~virtcoll/tuneup/browse.html
https://www.mdcalc.com/calc/3970/caprini-score-venous-thromboembolism-2005
https://www.medware.com.br/2024/04/calculadora-de-risco-cirurgico-pela-american-college-os-phisicians/
https://www.medware.com.br/2024/04/calculadora-de-risco-intriseco/
https://www.medware.com.br/2024/04/escore-emapo/
https://www.medware.com.br/2024/04/risco-cirurgico-multifatorial-de-goldman/
https://www.medware.com.br/2024/05/o-laudo-de-ecocardiograma-mais-completo/
https://www.meuip.com.br
https://www.meulivro.biz/anatomia/1514/atlas-de-anatomia-humana-em-imagem-4-ed-pdf/
https://www.meulivro.biz/categoria/hematologia/#google_vignette
https://www.minhavida.com.br/saude/tratamento/3870-imc
https://www.msdmanuals.com/pt/casa/dist%C3%BArbios-do-cora%C3%A7%C3%A3o-e-dos-vasos-sangu%C3%ADneos/diagn%C3%B3stico-de-dist%C3%BArbios-do-cora%C3%A7%C3%A3o-e-dos-vasos-sangu%C3%ADneos/teste-eletrofisiol%C3%B3gico
https://www.msdmanuals.com/pt/profissional/multimedia/clinical-calculator/percentis-do-%C3%ADndice-de-massa-corporal-imc-para-meninas-2-a-20-anos-de-idade
https://www.msdmanuals.com/pt/profissional/pages-with-widgets/calculadoras-cl%c3%adnicas?mode=list
https://www.msdmanuals.com/pt-br/profissional/multimedia/clinical-calculator/euroscore-para-avalia%C3%A7%C3%A3o-do-risco-de-cirurgia-card%C3%ADaca-vers%C3%A3o-cumulativa
https://www.msdmanuals.com/pt-br/profissional/t%C3%B3picos-especiais/cuidados-dos-pacientes-cir%C3%BArgicos/avalia%C3%A7%C3%A3o-pr%C3%A9-operat%C3%B3ria
https://www.phpclasses.org/
https://www.plugnmeet.org/
https://www.rccc.eu/Calculadoras.html
https://www.rededorsaoluiz.com.br/calculadora-de-imc
https://www.rededorsaoluiz.com.br/exames-e-procedimentos
https://www.rededorsaoluiz.com.br/hospital/sao-luiz-itaim/area-medica/documentos
https://www.santacasasjc.com.br/preparo-de-exames/
https://www.scymed.com/es/smnxab/smnxabaa.htm
https://www.scymed.com/es/smnxfd/smnxfdah.htm
https://www.socalculadoras.com/calcular-imc?gad_source=1&gclid=Cj0KCQjwgJyyBhCGARIsAK8LVLOEgT5FvUGDtb_iOFT6HqljhtuquoOyXOL9sz7U4xZyfy-myn4BnrkaAu3yEALw_wcB
https://www.sopterj.com.br/respirar/protocolos-sopterj/
https://www.stj.jus.br/sites/portalp/Paginas/Comunicacao/Noticias-antigas/2016/2016-05-16_14-57_Arrendatario-e-responsavel-pelas-multas-de-veiculos-de-arrendamento-mercantil.aspx
https://www.tabnews.com.br/jhowjhoe/como-enviar-mensagens-no-api-do-whatsapp-com-php-totalmente-de-graca
https://www.techno360.in/category/windows/
https://www.techno360.in/enable-taskbar-labels-in-windows-11/
https://www.techtiplib.com/tag/giveaways
https://www.ufrgs.br/telessauders/perguntas/como-realizar-avaliacao-cardiologica-dos-pacientes-que-serao-submetidos-cirurgias-nao-cardiacas-eletivas/
https://www.unimed.coop.br/viver-bem/saude-em-pauta?p_p_id=101&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_101_struts_action=%2Fasset_publisher%2Fview_content&_101_assetEntryId=227769&_101_type=content&_101_urlTitle=estatura-por-idade&inheritRedirect=false&redirect=https%3A%2F%2Fwww.unimed.coop.br%2Fviver-bem%2Fsaude-em-pauta%3Fp_p_id%3D101%26p_p_lifecycle%3D0%26p_p_state%3Dmaximized%26p_p_mode%3Dview%26_101_struts_action%3D%252Fasset_publisher%252Fview#Tabela%20de%20peso%20e%20altura%20por%20idade,%20de%203%20a%2018%20anos
https://www.unimedcampinas.com.br/blog/cuidado-continuo/entenda-o-que-e-calculo-imc-e-para-que-serve
https://www.vittude.com/blog/test/teste-depressao-ansiedade-stress-dass21/
https://www.vivaolinux.com.br/script/Codigo-de-Barras
https://www.vivaolinux.com.br/script/Gerando-Codigo-de-barras-no-padrao-brasileiro-compativel-com-Browsers-Linux-e-Windows
https://www.wplocker.com/
https://youtu.be/zjuYSBK2twU
https://zulip.com/hello/
*----------------------------------------------------------------------------------------------------------------*
VXB7J-OUUZ4-B7AXL-DUCQ6-0O1Q4
HW9OR-G5VDP-0RID9-2CVBU-OWAA3
VZXTF-VV8V9-EI976-0SQ02-TWXV0
DTSMS-8CB35-4HXOS-L1QMD-OD48G
Activation Code:
3XDMV-4I4PW-0Y7BP-DIME4-YLYEU
RCWVF-S981N-HJQ7P-4D4VZ-WAIEB
K1OLG-R3RTU-3MOV1-VK48K-VD7Z8
I1ZLE-H3ZMC-7LFBW-1KV07-OXQP9
Instructions for Installation:

Endereço: Próximo ao terminal urbano - R. Ermeti Simonetti, 84 - St. Central, Anápolis - GO, 75040-450
Telefone: (62) 99462-3721
27.030.609/0001-60

pontocomsistemasweb@gmail.com
novo2024novo2024

br3tecia@gmail.com
adm@@@353535
*----------------------------------------------------------------------------------------------------------------*
https://gist.github.com/fadevbr
user:fadevbr borda

dev.fabricio.br@gmail.com
*----------------------------------------------------------------------------------------------------------------*
https://cliente.branixhost.com.br/clientarea.php?action=productdetails&id=72
https://cliente.branixhost.com.br/login
sousagarcia@hotmail.com
d:aGz7rvHa1h
Ldn4076921232f297a57a5a743


Git
sousagarcia@hotmail.com
kb3pg#%Gmb@ke%z

sousgarciabarbosa@gmail.com
br3tecno@gmail.com
7KcWmEb^he

https://pontocomsistemas.com.br/wp-login.php
admin@pontocomsistemas.com.br
?7_NfRS2ai7lrmjy

https://chef.creator.com.br/project-list
sousagarcia@hotmail.com

d:aGz7rvHa1h
1 887 500 359 - PsiqueAns001
1 293 847 322 - Micromed01
  309 637 914
  692 437 355
*/
/////////
<?php


A validade de documentos médicos no Brasil pode variar bastante dependendo do tipo de documento e da finalidade para a qual ele será utilizado. Não há uma regra única para todos.

Aqui está um resumo dos prazos de validade mais comuns para alguns tipos de documentos médicos:

1. Prontuários Médicos:

Guarda: A legislação brasileira, como a Lei 13.787/18, estabelece que os prontuários médicos (sejam físicos, digitalizados ou eletrônicos) devem ser guardados por um período mínimo de 20 anos após o último atendimento.
Descarte: Após esse período, os prontuários podem ser descartados, mas com cuidado para garantir a proteção das informações dos pacientes.
2. Receitas Médicas:

Receita Simples: Geralmente, tem validade de 30 dias a partir da data de emissão.
Receita Antimicrobiana: Validade de 10 dias a partir da data de emissão.
Receita de Controle Especial (Portaria SVS/MS n° 344/98): Validade de 30 dias a partir da data de emissão.
Validade Nacional: Desde 2018 (Lei 13.732), todas as receitas médicas, inclusive as de medicamentos sujeitos a controle especial, têm validade nacional.
3. Atestados Médicos:

A validade de um atestado médico é determinada pelo médico que o emite e deve conter o prazo de afastamento ou justificativa.
Emissão: A Resolução CFM n° 2.381/24 normatiza a emissão de diversos documentos médicos, incluindo atestados. A partir de março de 2025, a plataforma Atesta CFM passará a ser obrigatória para a emissão e validação de atestados médicos, visando combater fraudes.
Para o trabalho (CLT): O empregado tem o direito de se afastar do trabalho por até 15 dias consecutivos mediante atestado médico. Se a incapacidade persistir por mais de 15 dias, a situação pode envolver o INSS.
Para o INSS: Os atestados médicos são cruciais para solicitação de benefícios, e os prazos de envio e aceitação podem variar conforme a situação do afastamento.
4. Laudos Médicos:

A validade de um laudo médico pode variar bastante dependendo da finalidade e da instituição que o solicita.
Para o INSS: Laudos periciais destinados ao INSS costumam ter validade de até 90 dias a partir da data de assinatura.
Para a Justiça: Em contextos judiciais, a validade pode ser de até 180 dias.
Conselho Federal de Medicina (CFM): Para o CFM, um laudo não pode ser recusado apenas por motivo de data de validade, a menos que haja suspeita de falsificação. Não há uma previsão legal ou ética para que o médico especifique a validade de um laudo, sendo as instituições que exigem o laudo que geralmente definem seus prazos de aceitação.
5. Pedidos de Exames Médicos:

Pedidos médicos para exames complementares podem ter validade de até 180 dias. No entanto, é importante verificar com a instituição ou convênio específico, pois alguns ainda podem considerar prazos menores (ex: 90 dias).
Exames específicos: Alguns exames podem ter validade mais curta, como:
Exames laboratoriais (sangue, fezes e urina): geralmente 3 meses.
Consulta Oftalmológica e avaliação (acuidade visual): 12 meses.
Exames de imagem (mamografia, etc.) e Citologia cérvico-vaginal oncótica: 12 meses.
Importante:

Sempre verifique a data de emissão do documento e as exigências específicas da instituição (empresa, INSS, plano de saúde, etc.) para a qual o documento será apresentado, pois os prazos podem variar.
Documentos médicos eletrônicos ou digitalizados para ter validade legal, devem ser assinados digitalmente seguindo as normas da ICP-Brasil.



////////////////////////
A Resolução elenca 13 documentos principais:

Atestado Médico de Afastamento: documento simplificado que indica a quantidade de dias de dispensa necessários para a recuperação do paciente;
Atestado de Acompanhamento: confirma a presença de um acompanhante durante consulta ou procedimento médico;
Declaração de Comparecimento: documento fornecido pelo setor administrativo de estabelecimento de saúde, válido como justificativa de ausência no trabalho;
Atestado de Saúde: afirma a condição de saúde física e mental do paciente, utilizado em diversas situações, como licença-maternidade e viagens aéreas;
Atestado de Saúde Ocupacional (ASO): avalia a aptidão ou inaptidão do trabalhador para suas atividades laborativas, conforme normas do Ministério do Trabalho e Emprego;
Declaração de Óbito
Relatório Médico Circunstanciado: documento detalhado sobre o estado de saúde do paciente;
Relatório Médico Especializado: documento detalhado sobre o estado de saúde do paciente, geralmente solicitados para fins de perícia;
Parecer Técnico: : documento expedido por médico especialista em área específica, de caráter opinativo, baseado na literatura científica;
Laudo Médico-Pericial: documento técnico utilizado em um processo judicial;
Laudo Médico: descrição e conclusão do médico sobre exame complementar realizado em um paciente;
Solicitação de Exames
Resumo ou Sumário de Alta: relatório clínico elaborado quando o paciente recebe alta.
/////////

SUGESTÃO DE CONTRATO PARA PRESTAÇÃO DE SERVIÇOS PSICOLÓGICOS ENTRE AS PARTES A SEGUIR IDENTIFICADAS

 

1. DAS PARTES


1.1. PRESTADORA DE SERVIÇOS (CONTRATADA): NOME, QUALIFICAÇÃO (pessoa física ou jurídica) ......... ....................... ............................. , domiciliada .................................................., neste ato representada por sua sócia-gerente (ou pela psicóloga), ....................................., CRP-01 n.º............., e do CPF n°. (ou CNPJ)


1.2. TOMADOR DE SERVIÇOS (CONTRATANTE): NOME, QUALIFICAÇÃO (pessoa física ou jurídica), nacionalidade, estado civil, profissão, portador da CI nº    , e do CPF nº     , residente e domiciliado ...........................................Brasília – DF.


2. OBJETO


O Psicólogo deverá redigir todos os dados sobre o trabalho que irá prestar, o mais amplo e detalhado possível, tais como:


Este Contrato visa assegurar ao paciente seus direitos e deveres frente ao acompanhamento psicoterápico.....


1 – Sigilo 
a) Todos os conteúdos das sessões serão de extremo sigilo, conforme prevê o código de ética do psicólogo;
b) As sessões não serão gravadas nem acompanhadas por terceiros;* vide artigo 14 CEPP
c) Todo material produzido em sessão é de responsabilidade do psicólogo, bem como seu arquivamento, priorizando sempre o sigilo das informações;** vide Artigo 4º da Resolução 01/2009.
d) O contato com familiares ou terceiros, seja para adquirir novas informações ou esclarecimentos, ocorrerá somente com consentimento do paciente e deverá ser discutido previamente na sessão;
e) Caso seja necessário entrar em contato com outros profissionais que acompanham o paciente, o mesmo deverá ser informado previamente;

2 – Sessões
a) Cada sessão terá XX minutos de duração;
b) Em caso de atraso o paciente perderá no tempo de duração da sessão, porém o valor cobrado permanecerá o mesmo; *
c) Caso necessite faltar à sessão o paciente deverá entrar em contato com o Psicólogo com antecedência de no mínimo 24 horas para remarcar a consulta;*
d) A primeira sessão será marcada no dia e horário conforme disponibilidade do paciente, as sessões seguintes terão dias e horários fixos para evitar faltas e esquecimentos;
e) Caso haja necessidade de mudança do dia e horário o Psicólogo deverá ser informado com antecedência durante a sessão para que uma nova data seja estabelecida;
f) O paciente tem a liberdade de desistir do acompanhamento psicoterápico em qualquer momento sem quaisquer ônus;
g) Em caso de desistência do acompanhamento psicoterápico o paciente deverá informar na penúltima ou no início da ultima sessão para que seja feito o encerramento adequadamente;
h) É importante frisar que a frequência ideal das sessões consiste em um encontro semanal.

3 – Duração do Acompanhamento Psicoterápico
a) O tempo mínimo da avaliação psicodiagnóstica é de X sessões;
b) ........

3. DA AVALIAÇÃO
Esta cláusula é para o caso de haver necessidade de uma avaliação inicial do paciente, para diagnóstico e programação do trabalho.

4. DIREITOS E OBRIGAÇÕES DO CONTRATANTE
Neste tópico será inserido comprometimentos, tais como, comparecer às sessões, não se atrasar, reposição ou não de atendimentos, tolerância na espera do paciente, solicitação prévia do paciente para mudanças de horários, as implicações que poderão ocorrer, principalmente financeiras, caso o paciente abandone o tratamento, dentre outras necessitadas da categoria.

5. DAS OBRIGAÇÕES DA CONTRATADA
Nesta cláusula conterá a obrigação do psicólogo de prestar os serviços constantes da cláusula n.º 2, no horário avençado, de repor as sessões, dentre outras necessidades apreciadas pela categoria.

6. DO PAGAMENTO
O(A) Contratante deverá pagar ao(a) Contratada a quantia de R$     , (valor da sessão), de que forma, em caso de atraso os juros devidos e etc........

7. DO PRAZO DE VIGÊNCIA
O período em que vigerá o contrato entre as partes, tal como:
O acompanhamento não tem um tempo máximo determinado para acabar, dependendo apenas da evolução do paciente.

8. DA RESCISÃO
O presente contrato poderá ser rescindido por ambas as partes, por mútuo acordo, ou desde que a parte interessada manifeste a intenção de dissolver a presente relação contratual, por notificação expressa à outra parte, com antecedência mínima de 30 (trinta) dias.

9. DISPOSIÇÕES GERAIS
Neste tópico conterá aspectos, tais como, ser vedado fumar nas dependências da Contratada, portar arma, responsabilidade por danos à Contratada causados pelo paciente e etc. No mais, poderá inserir os seguintes itens. 
O descumprimento de quaisquer das cláusulas referentes a este contrato suscita a responsabilização do responsável, nos termos da legislação em vigor.
A contratada não se responsabiliza por objetos, valores, documentos ou qualquer outro bem pertencente ao Contratante deixados no espaço físico da Contratada.
Em caso de acidente do Contratante, nas dependências da Contratada, sem que esta última lhe tenha dado causa, fica a Contratada autorizada a encaminhar o Contratante para respectivo atendimento médico-hospitalar, ficando a cargo deste o pagamento das despesas que se fizerem necessárias.

10. FORO DE ELEIÇÃO
As partes elegem o foro da Circunscrição Judiciária de Brasília – DF para dirimirem quaisquer dúvidas decorrentes do presente contrato.
E, por estarem, assim, de comum acordo, as partes assinam o presente contrato em duas vias de igual teor.

 

Brasília,    de                de 

_____________
Contratante

_____________
Contratada

_____________
Testemunha

_____________
Testemunha

Contrato de Prestação de Serviços Psicológicos

Pelo presente Instrumento, de um lado como CONTRATADO/PSICÓLOGO, ESTEVAM COLACICCO HOLPERT, brasileiro, casado, psicólogo, inscrito no Conselho Regional de Psicologia sob o nº CRP 06/65.368, cadastro e-psi 219567, inscrito no CPF/MF nº 280.913.848-66, residente e domiciliado no endereço Rua Antonio Devair Pinheiro, nº 230, bairro Campos Eliseos, Brotas/SP, CEP 17380-000, de outro lado como CONTRATANTE/PACIENTE, QUALIFICAÇÃO COMPLETA DO CLIENTE MENCIONANDO SEU REPRESENTANTE LEGAL SE FOR O CASO, têm entre si, justo e contratado, o presente instrumento que se regerá mediante as seguintes cláusulas, termos e condições:

Cláusula Primeira – Do Objeto do Contrato:

1.1. Este contrato destina-se a regimentar a prestação de serviço de atendimento psicológico de orientação psicanalítica a adultos e adolescentes de treze a dezessete anos, por meio do uso de Tecnologia da Informação e Comunicação (Resolução nº 11/2018 do Conselho Federal de Psicologia), de forma síncrona, pelo profissional Estevam Colacicco Holpert (CRP 06/65.368, cadastro e-psi 219567), em congruência com o Código de Ética Profissional do Psicólogo (Resolução CFP 010/2005) e demais legislações e orientações vinculadas ao exercício profissional da psicologia no Brasil.

Cláusula Segunda – Do Público Alvo:
2.1. Adultos e adolescentes de treze a dezessete anos que se sintam familiarizados e habilitados a utilizar TIC – Tecnologias da Informação e Comunicação – como recurso para a promoção de saúde mental. Em consonância com a Resolução CFP nº 11/2018, o profissional obriga-se a recusar o atendimento e/ou encaminhar o solicitante para um serviço de atendimento psicológico ou de saúde mental presencial, ou especializado, em casos de psicopatologias graves, urgentes, situação de violação de direitos ou que extrapolem as possibilidades do atendimento virtual. A recusa também ocorrerá caso o solicitante não demonstre familiaridade mínima ou grande desconforto com o uso da tecnologia envolvida em chamadas de vídeo, ausência das condições técnicas ou ambiente físico inadequado.

Cláusula Terceira – Da Duração do Serviço:
3.1. As sessões de atendimento têm duração primordial de sessenta minutos, podendo ser estendidas ou abreviadas de acordo com critérios técnicos relacionados ao manejo e exigências do caso, sem qualquer alteração no valor da sessão. A prestação do serviço psicológico não tem limite pré-estabelecido no número de sessões, e pode ser interrompida a qualquer momento por iniciativa unilateral das partes, ou em comum acordo, sem quaisquer obrigações ou direitos decorrentes.

Cláusula Quarta – Do Sigilo:
4.1. O sigilo do atendimento deve ser guardado pela utilização de serviços criptografados de troca de mensagens e chamadas de vídeo, pelo estabelecimento de um ambiente físico adequado e reservado, e pela manutenção permanente do computador ou telefone celular, por meio de programas antivírus, ‘firewall’ e atualizações necessárias. Fica expressamente proibida, por qualquer uma das partes, a gravação parcial ou integral da sessão, seja por voz, vídeo, imagem, ou qualquer outro meio, criptografado ou não, sob pena das sanções previstas em lei.

Cláusula Quinta – Do Registro e Armazenamento do Material das Sessões:
5.1. O registro e armazenamento do material das sessões dar-se-á em consonância com as Resoluções 01/2009 e 05/2010 do Conselho Federal de Psicologia, e demais legislações e orientações vinculadas ao exercício profissional da psicologia no Brasil.

Cláusula Sexta – Do Valor das Sessões:
6.1. As sessões de atendimento têm valor individual estabelecido de acordo com a Tabela de Referência Nacional de Honorários dos Psicólogos (https://www.crpsp.org/pagina/view/65), publicada pelo Conselho Regional de Psicologia-São Paulo. O valor cobrado atualmente é de noventa reais por sessão.

Cláusula – Setima Do Pagamento:
7.1. O pagamento da sessão deve ser realizado por meio de depósito bancário, PIX ou pelo sistema ‘Pag Seguro’, com antecedência mínima de vinte e quatro horas do horário de início da sessão.

Cláusula Oitava – Do Cancelamento e Reagendamento da Sessão:

8.1. Caso o cancelamento seja informado ao psicólogo com antecedência mínima de vinte e quatro horas do início da sessão, o valor pago será reembolsado, ou a sessão será reagendada, a critério do cliente. Nos casos em que o aviso for realizado com antecedência menor que vinte e quatro horas do início da sessão, caso não haja aviso, ou na ausência do cliente à sessão agendada, não haverá reembolso. Caso o reagendamento ocorra por iniciativa do profissional, não haverá custo extra ao cliente.

Cláusula Nona – Do Atraso à Sessão:

9.1. Em caso de atraso por parte do cliente, não haverá reposição do tempo ou reembolso correspondente ao período de atraso. Caso o atraso ocorra por parte do psicólogo, o tempo correspondente será acrescentado ao período imediatamente posterior à sessão, sem a possibilidade de reembolso.

Cláusula Décima – Do Termo de Autorização para o Atendimento de Adolescentes:

10.1 No caso de clientes adolescentes (de treze a dezessete anos completos), o Termo de Autorização para o Atendimento Psicológico Online de Adolescente deve ser preenchido e assinado por ao menos um dos pais e/ou responsáveis legais (com documentação comprovada), e enviado ao psicólogo antecipadamente ao início da sessão.

Cláusula Décima Primeira – Do Foro
11.1. As partes contratantes, de comum acordo, elegem o Foro da Comarca de Brotas, para que por meio dele e ação competente, venham a ser dirimidas todas as dúvidas ou questões oriundas deste instrumento, arcando, a parte que for julgada vencida, com o pagamento das custas processuais e honorários advocatícios da parte vencedora


Autorização para Emissão de Boletos
Eu, [Nome Completo do Paciente], inscrito(a) no CPF sob o número [Número do CPF do Paciente], residente e domiciliado(a) na [Endereço Completo do Paciente], telefone [Telefone do Paciente] e e-mail [E-mail do Paciente], por meio deste instrumento, autorizo a [Nome da Clínica], inscrita no CNPJ sob o número [Número do CNPJ da Clínica], localizada na [Endereço Completo da Clínica], a emitir boletos bancários em meu nome para cobrança dos valores referentes aos procedimentos, consultas e/ou serviços médicos por mim utilizados nesta clínica.

Declaro estar ciente e de acordo com as seguintes condições:

Finalidade: Os boletos serão emitidos exclusivamente para a cobrança de débitos oriundos dos serviços prestados pela clínica.

Dados para Emissão: Comprometo-me a fornecer todos os dados cadastrais necessários e atualizados para a correta emissão dos boletos.

Envio: Os boletos serão enviados preferencialmente para o e-mail [E-mail do Paciente] e/ou para o endereço [Endereço Completo do Paciente], conforme minha opção no momento da contratação dos serviços ou a critério da clínica.

Pagamento: O pagamento dos boletos deverá ser efetuado até a data de vencimento, sob pena de incidência de juros, multa e demais encargos legais, conforme previsto na legislação vigente.

Validade: Esta autorização é válida por tempo indeterminado ou até que seja formalmente revogada por uma das partes, mediante comunicação por escrito.

Estou ciente de que a emissão de boletos é uma forma de facilitação de pagamento e concordo em cumprir com as obrigações financeiras decorrentes dos serviços que me forem prestados.

Por ser a expressão da verdade, firmo o presente.

[Localidade, ex: Uberlândia - MG], [Dia] de [Mês] de [Ano].

Assinatura do Paciente:

[Nome Completo do Paciente]


Autorização para Emissão de Boletos Bancários
Eu, [Nome Completo do Paciente], [Nacionalidade], [Estado Civil], portador(a) do RG nº [Número do RG] e CPF nº [Número do CPF], residente e domiciliado(a) na [Endereço Completo: Rua, Número, Bairro, Cidade, Estado, CEP], doravante denominado(a) AUTORIZANTE,

AUTORIZO a clínica psicológica [Nome Completo da Clínica], CNPJ nº [Número do CNPJ da Clínica], com sede em [Endereço Completo da Clínica], doravante denominada CLÍNICA, a emitir boletos bancários em meu nome para o pagamento das sessões de psicoterapia e/ou outros serviços psicológicos prestados.

Declaro estar ciente e de acordo com as seguintes condições:

Finalidade: Os boletos serão emitidos exclusivamente para a cobrança dos valores referentes aos serviços psicológicos contratados e devidamente prestados pela CLÍNICA.

Periodicidade e Valores: A emissão dos boletos seguirá a periodicidade e os valores acordados previamente em contrato de prestação de serviços ou termo de adesão, referente às sessões ou pacotes de serviços.

Vencimento: Os boletos terão data de vencimento a ser definida pela CLÍNICA, em comum acordo com o AUTORIZANTE, e estarão sujeitos às políticas de cobrança da CLÍNICA em caso de atraso, incluindo, mas não se limitando a, juros e multas, conforme previsto em lei.

Envio dos Boletos: Os boletos poderão ser enviados por [Escolha uma ou mais opções e, se houver, detalhe: e-mail para o endereço X, WhatsApp para o número Y, ou disponibilizados para retirada na recepção da CLÍNICA]. É responsabilidade do AUTORIZANTE manter seus dados de contato atualizados.

Revogação: Esta autorização poderá ser revogada a qualquer tempo pelo AUTORIZANTE, mediante comunicação escrita à CLÍNICA, com antecedência mínima de [Número] dias. A revogação não isenta o AUTORIZANTE de quitar débitos já gerados ou em aberto até a data efetiva da revogação.

Confidencialidade: A CLÍNICA se compromete a manter a confidencialidade dos dados do AUTORIZANTE, utilizando-os apenas para os fins desta autorização e em conformidade com a Lei Geral de Proteção de Dados (LGPD).

Ao assinar este documento, o AUTORIZANTE declara ter lido e compreendido todas as cláusulas e concorda com os termos aqui estabelecidos.

[Cidade], [Dia] de [Mês] de [Ano].

AUTORIZANTE:

[Nome Completo do Paciente]
CPF: [Número do CPF do Paciente]

CLÍNICA PSICOLÓGICA:

[Nome do Representante Legal da Clínica]
[Cargo do Representante Legal da Clínica]
[Nome Completo da Clínica]
CNPJ: [Número do CNPJ da Clínica]

/////////////////////////////////
<?php
class JitsiMeetView02 extends TPage
{
    protected $form;
    private static $formName = 'form_JitsiMeetView02';

    private function formatCnpjCpf($value)
    {
        $value = preg_replace('/[^0-9]/', '', $value);
        if (strlen($value) == 11) {
            return preg_replace('/(\d{3})(\d{3})(\d{3})(\d{2})/', '$1.$2.$3-$4', $value);
        } elseif (strlen($value) == 14) {
            return preg_replace('/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/', '$1.$2.$3/$4-$5', $value);
        }
        return $value;
    }

    public function __construct($param = null)
    {

        parent::__construct();

        if (!empty($param['target_container'])) {
            $this->adianti_target_container = $param['target_container'];
        }

        $this->form = new BootstrapFormBuilder(self::$formName);
        $this->form->setFormTitle("Videoconferência");

        $room_name = new TEntry('room_name');
        $room_name->setTip('Nome da sala gerado automaticamente, mas editável');
        $room_name->setSize('100%');
        $room_name->setProperty('required', 'true');

        $user_name = new TCombo('user_name');
        $user_name->setTip('Profissional de saúde (definido automaticamente)');
        $user_name->setSize('100%');
        $user_name->setProperty('required', 'true');
        $user_name->setEditable(false);

        $paciente_id = new TDBCombo('paciente_id', 'bdgestorweb', 'Pessoa', 'id', '{id}', 'nome asc');
        $paciente_id->setTip('Digite para pesquisar o paciente por nome');
        $paciente_id->setSize('100%');
        $paciente_id->setProperty('required', 'true');
        $paciente_id->setEditable(false);
        $paciente_id->enableSearch();

        $empresa_id = new TCombo('empresa_id');
        $empresa_id->setTip('Empresa (definida automaticamente)');
        $empresa_id->setSize('100%');
        $empresa_id->setProperty('required', 'true');
        $empresa_id->setEditable(false);

        $hidden_paciente_id = new TEntry('hidden_paciente_id');
        $hidden_paciente_id->setProperty('type', 'hidden');

        try {
            TTransaction::open('bdgestorweb');
            TTransaction::log('Carregando dados para combos no __construct');

            if (class_exists('SystemUsers')) {
                $users = SystemUsers::all();
                $user_items = [];
                foreach ($users as $user) {
                    $user_items[$user->id] = $user->name;
                }
                $user_name->addItems($user_items);
            } else {
                throw new Exception('Classe SystemUsers não encontrada.');
            }

            if (class_exists('Pessoa')) {
                $pessoas = Pessoa::orderBy('nome', 'asc')->get();
                $paciente_items = [];
                foreach ($pessoas as $pessoa) {
                    $cnpj_cpf = $pessoa->cnpj_cpf ? $this->formatCnpjCpf($pessoa->cnpj_cpf) : '';
                    $paciente_items[$pessoa->id] = "{$pessoa->id} - {$pessoa->nome} {$cnpj_cpf}";
                }
                $paciente_id->addItems($paciente_items);
            } else {
                throw new Exception('Classe Pessoa não encontrada.');
            }

            if (class_exists('SystemUnit')) {
                $empresas = SystemUnit::all();
                $empresa_items = [];
                foreach ($empresas as $empresa) {
                    $empresa_items[$empresa->id] = $empresa->name;
                }
                $empresa_id->addItems($empresa_items);
            } else {
                throw new Exception('Classe SystemUnit não encontrada.');
            }

            TTransaction::close();
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro ao carregar dados no __construct: ' . $e->getMessage());
            new TMessage('error', '0013 - Erro ao carregar dados: ' . $e->getMessage());
        }

        $this->form->addFields([new TLabel('Empresa:', '#333', 12, 'b'), $empresa_id]);
        $this->form->addFields([new TLabel('Nome da sala:', '#333', 12, 'b'), $room_name]);
        $this->form->addFields([new TLabel('Profissional de saúde:', '#333', 12, 'b'), $user_name]);
        $this->form->addFields([new TLabel('Paciente:', '#333', 12, 'b'), $paciente_id]);
        $this->form->addFields([$hidden_paciente_id]);

        $this->form->addAction('Entrar na consulta', new TAction([$this, 'joinMeeting']), 'fa:video-camera blue');

        $info_container = new TElement('div');
        $info_container->id = 'info-container';
        $info_container->style = 'width: 100%; margin-bottom: 10px; display: none; background-color: #f8f9fa; padding: 10px; border-radius: 5px;';
        $info_container->add('<h5>INFORMAÇÕES SOBRE A CONSULTA DO PACIENTE: <span id= "display-paciente-codigo"></span> - <span id= "display-paciente-nome"></span></h5>');
        $info_container->add('<p><strong>SALA:</strong> <span id="display-room-name"></span> - <strong>INICIO:</strong> <span id="display-horainicial-conexao"></span> <strong>PREVISÃO DE TERMINO:</strong> <span id="display-vhorariofinal"></span></p>');
        $info_container->add('<p><strong>LINK PARA O ACESSO:</strong> <span id="display-patient-link"></span></p>');
        $info_container->add('<p><strong>PROFISSIONAL:</strong> <span id= "display-profissional-nome"></span> - <strong>MEU TEMPO DE ATENDIMENTO:</strong> <span id= "display-tempo-atendimento"></span></p>');

        $jitsi_container = new TElement('div');
        $jitsi_container->id = 'jitsi-container';
        $jitsi_container->style = 'width: 100%; height: 600px; margin-top: 20px; display: none; border: 1px solid #ccc; border-radius: 5px;';

        $button_container = new TElement('div');
        $button_container->style = 'margin-bottom: 10px;';

        $btn_leave = new TElement('button');
        $btn_leave->type = 'button';
        $btn_leave->id = 'btn-leave';
        $btn_leave->class = 'btn btn-danger';
        $btn_leave->style = 'display: none; margin-right: 10px;';
        $btn_leave->onclick = "window.leaveJitsiMeeting('profissional', '" . TSession::getValue('userid') . "')";
        $btn_leave->add(new TImage('fa:sign-out red'));
        $span_leave = new TElement('span');
        $span_leave->add(' Sair da consulta');
        $btn_leave->add($span_leave);

        $btn_observation = new TElement('button');
        $btn_observation->type = 'button';
        $btn_observation->id = 'btn-observation';
        $btn_observation->class = 'btn btn-primary';
        $btn_observation->style = 'display: none;';
        $btn_observation->onclick = "window.openObservationForm()";
        $btn_observation->add(new TImage('fa:notes-medical blue'));
        $span_observation = new TElement('span');
        $span_observation->add(' Cadastrar Observação');
        $btn_observation->add($span_observation);

        $button_container->add($btn_leave);
        $button_container->add($btn_observation);

        $vbox = new TVBox;
        $vbox->style = 'width: 100%';
        $vbox->class = 'form-container';

        if (empty($param['target_container'])) {
            $vbox->add(TBreadCrumb::create(["Saúde", "Videoconferência"]));
        }

        $vbox->add($info_container);
        $vbox->add($button_container);
        $vbox->add($this->form);
        $vbox->add($jitsi_container);

        parent::add($vbox);

        $this->addJavaScript();

        $this->onLoad($param);
    }

    private function generateRoomName()
    {
        $prefix = 'Consulta';
        $timestamp = date('Ymd-His');
        $random = substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 4);
        return strtoupper($prefix . '-' . $timestamp . '-' . $random);
    }

    public function showMainForm($param = null)
    {
        TTransaction::log('Iniciando showMainForm');
        try {
            $meeting_data = TSession::getValue('meeting_data');
            if ($meeting_data && !empty($meeting_data['room_name'])) {
                TSession::setValue('participant_mapping_' . $meeting_data['room_name'], null);
                if (TSession::getValue('userid')) {
                    $this->logExitOnReset($meeting_data['room_name'], TSession::getValue('userid'), 'profissional');
                }
                if ($meeting_data['paciente_id']) {
                    $this->logExitOnReset($meeting_data['room_name'], $meeting_data['paciente_id'], 'paciente');
                }
            }
            TSession::setValue('meeting_data', null);
            TSession::setValue('patient_link', null);
            TSession::setValue('log_videoconferencia_id_profissional', null);
            TSession::setValue('log_videoconferencia_id_paciente_' . ($meeting_data['paciente_id'] ?? ''), null);

            $this->form->style = 'display: block;';
            
            $userid = TSession::getValue('userid');
            $userunitid = TSession::getValue('userunitid');
            
            $data = new stdClass();
            $data->room_name = $this->generateRoomName();
            $data->user_name = $userid ? $userid : null;
            $data->empresa_id = $userunitid ? $userunitid : null;
            $data->paciente_id = null;
            $data->hidden_paciente_id = null;
            $this->form->setData($data);
            TTransaction::log('Formulário inicializado com dados: ' . json_encode($data));
            
            TScript::create("
                try {
                    var jitsiContainer = document.getElementById('jitsi-container');
                    if (jitsiContainer) {
                        jitsiContainer.innerHTML = '';
                        jitsiContainer.style.display = 'none';
                        console.log('jitsi-container limpo e ocultado');
                    }
                    var infoContainer = document.getElementById('info-container');
                    if (infoContainer) {
                        infoContainer.style.display = 'none';
                        document.getElementById('display-room-name').textContent = '';
                        document.getElementById('display-paciente-codigo').textContent = '';
                        document.getElementById('display-paciente-nome').textContent = '';
                        document.getElementById('display-profissional-nome').textContent = '';
                        document.getElementById('display-tempo-atendimento').textContent = '';
                        document.getElementById('display-patient-link').textContent = '';
                        document.getElementById('display-horainicial-conexao').textContent = '';
                        document.getElementById('display-vhorariofinal').textContent = '';
                        console.log('info-container limpo e ocultado');
                    }
                    var btnLeave = document.getElementById('btn-leave');
                    if (btnLeave) {
                        btnLeave.style.display = 'none';
                        console.log('Botão Sair ocultado');
                    }
                    var btnObservation = document.getElementById('btn-observation');
                    if (btnObservation) {
                        btnObservation.style.display = 'none';
                        console.log('Botão Observação ocultado');
                    }
                    var mainForm = document.forms['form_JitsiMeetView02'];
                    if (mainForm) {
                        mainForm.style.display = 'block';
                        mainForm.reset();
                        var pacienteIdField = mainForm.querySelector('[name=paciente_id]');
                        if (pacienteIdField) {
                            pacienteIdField.value = '';
                            console.log('Campo paciente_id resetado');
                        }
                        var hiddenPacienteIdField = mainForm.querySelector('[name=hidden_paciente_id]');
                        if (hiddenPacienteIdField) {
                            hiddenPacienteIdField.value = '';
                            console.log('Campo hidden_paciente_id resetado');
                        }
                        console.log('Formulário principal reexibido e reiniciado');
                    } else {
                        console.error('Formulário principal não encontrado');
                        __adianti_message('0004 - Erro: Formulário principal não encontrado.', function() {});
                    }
                    if (window.jitsiApi) {
                        window.jitsiApi.executeCommand('hangup');
                        window.jitsiApi.dispose();
                        window.jitsiApi = null;
                        console.log('Jitsi API finalizada ao reexibir formulário');
                    }
                    console.log('Formulário reiniciado para nova consulta');
                } catch (e) {
                    console.error('Erro em showMainForm:', e);
                    __adianti_message('0005 - Erro ao reiniciar o formulário: ' + e.message, function() {});
                }
            ");
        } catch (Exception $e) {
            TTransaction::log('Erro em showMainForm: ' . $e->getMessage());
            new TMessage('error', '0005 - Erro ao reiniciar o formulário: ' . $e->getMessage());
        }
    }

    private function logExitOnReset($room_name, $pessoa_id, $participant_type)
    {
        TTransaction::log('Iniciando logExitOnReset para room_name: ' . $room_name . ', pessoa_id: ' . $pessoa_id . ', tipo: ' . $participant_type);
        try {
            TTransaction::open('bdgestorweb');

            $log_id = TSession::getValue('log_videoconferencia_id_' . $participant_type);
            if (!$log_id && $participant_type === 'paciente') {
                $log_id = TSession::getValue('log_videoconferencia_id_paciente_' . $pessoa_id);
            }
            if (!$log_id) {
                $log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                         ->where('pessoa_id', '=', $pessoa_id)
                                         ->where('datafim_conexao', '=', null)
                                         ->first();
                if ($log) {
                    $log_id = $log->id;
                }
            }

            if ($log_id) {
                $log = LogVideoconferencia::find($log_id);
                if ($log && !$log->datafim_conexao) {
                    $log->datafim_conexao = date('Y-m-d');
                    $log->horafim_conexao = date('H:i:s');
                    $log->store();
                    TSession::setValue('log_videoconferencia_id_' . $participant_type, null);
                    if ($participant_type === 'paciente') {
                        TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa_id, null);
                    }
                    TTransaction::log('Log de saída atualizado em logExitOnReset para log_id: ' . $log_id . ', pessoa_id: ' . $pessoa_id);
                }
            }

            TTransaction::close();
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em logExitOnReset: ' . $e->getMessage());
        }
    }

    private function addJavaScript()
    {
        $user_name = 'Usuário';
        try {
            TTransaction::open('bdgestorweb');
            if (TSession::getValue('userid') && class_exists('SystemUsers')) {
                $user = SystemUsers::find(TSession::getValue('userid'));
                if ($user) {
                    $user_name = htmlspecialchars($user->name, ENT_QUOTES, 'UTF-8');
                }
            }
            TTransaction::close();
            TTransaction::log('Nome do usuário obtido: ' . $user_name);
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro ao obter nome do usuário: ' . $e->getMessage());
        }

        $script = <<<JS
        console.log('Script JavaScript carregado pelo addJavaScript para usuário: {$user_name}');
        console.log('__adianti_load_page está definida?', typeof __adianti_load_page !== 'undefined');
        if (!window.JitsiMeetExternalAPI) {
            console.log('Carregando JitsiMeetExternalAPI');
            var script = document.createElement("script");
            script.src = "https://meet.jit.si/external_api.js";
            script.async = true;
            script.onload = function() {
                console.log('JitsiMeetExternalAPI carregada com sucesso');
            };
            script.onerror = function() {
                console.error('Erro ao carregar JitsiMeetExternalAPI');
                __adianti_message('0006 - Erro ao carregar a biblioteca Jitsi. Verifique sua conexão e tente novamente mais tarde.', function() {});
            };
            document.head.appendChild(script);
        } else {
            console.log('JitsiMeetExternalAPI já está carregada');
        }

        window.leaveJitsiMeeting = function(participantType, pessoaId) {
            console.log('Iniciando leaveJitsiMeeting para ' + participantType + ', pessoaId: ' + (pessoaId || 'N/A'));
            try {
                var xhr = new XMLHttpRequest();
                var url = 'index.php?class=JitsiMeetView02&method=onExitMeeting&participant_type=' + participantType + '&pessoa_id=' + encodeURIComponent(pessoaId);
                console.log('Enviando requisição AJAX para:', url);
                xhr.open('POST', url, true);
                xhr.setRequestHeader('Content-Type', 'application/json');
                xhr.send();
                xhr.onload = function() {
                    console.log('Resposta do servidor:', xhr.responseText);
                    try {
                        var response = JSON.parse(xhr.responseText);
                        if (response.status === 'success') {
                            __adianti_toast('success', '{$user_name} - Encerrando consulta...', '', 3000);
                            __adianti_load_page('index.php?class=PessoaHistoricoListPsicologo');
                        } else {
                            console.error('Erro na resposta do servidor:', response.message);
                            __adianti_toast('error', '0008 - Erro ao sair da consulta: ' + response.message, '', 3000);
                            __adianti_load_page('index.php?class=JitsiMeetView02&method=showMainForm');
                        }
                    } catch (e) {
                        console.error('Erro ao processar resposta do servidor:', e);
                        __adianti_toast('error', '0008 - Erro ao sair da consulta: ' + e.message, '', 3000);
                        __adianti_load_page('index.php?class=JitsiMeetView02&method=showMainForm');
                    }
                };
                xhr.onerror = function() {
                    console.error('Erro na requisição AJAX');
                    __adianti_toast('error', '0008 - Erro ao sair da consulta: Falha na comunicação com o servidor.', '', 3000);
                    __adianti_load_page('index.php?class=JitsiMeetView02&method=showMainForm');
                };

                if (window.jitsiApi) {
                    window.jitsiApi.executeCommand('hangup');
                    window.jitsiApi.dispose();
                    console.log('Jitsi API finalizada com sucesso');
                    window.jitsiApi = null;
                } else {
                    console.warn('Nenhuma instância do Jitsi API encontrada para finalizar');
                }

                var jitsiContainer = document.getElementById('jitsi-container');
                if (jitsiContainer) {
                    jitsiContainer.innerHTML = '';
                    jitsiContainer.style.display = 'none';
                    console.log('jitsi-container limpo e ocultado');
                }
                var infoContainer = document.getElementById('info-container');
                if (infoContainer) {
                    infoContainer.style.display = 'none';
                    document.getElementById('display-room-name').textContent = '';
                    document.getElementById('display-paciente-codigo').textContent = '';
                    document.getElementById('display-paciente-nome').textContent = '';
                    document.getElementById('display-profissional-nome').textContent = '';
                    document.getElementById('display-tempo-atendimento').textContent = '';
                    document.getElementById('display-patient-link').textContent = '';
                    document.getElementById('display-horainicial-conexao').textContent = '';
                    document.getElementById('display-vhorariofinal').textContent = '';
                    console.log('info-container limpo e ocultado');
                }
                var btnLeave = document.getElementById('btn-leave');
                if (btnLeave) {
                    btnLeave.style.display = 'none';
                    console.log('Botão Sair ocultado');
                }
                var btnObservation = document.getElementById('btn-observation');
                if (btnObservation) {
                    btnObservation.style.display = 'none';
                    console.log('Botão Observação ocultado');
                }
                var mainForm = document.forms['form_JitsiMeetView02'];
                if (mainForm) {
                    mainForm.style.display = 'block';
                    mainForm.reset();
                    var pacienteIdField = mainForm.querySelector('[name=paciente_id]');
                    if (pacienteIdField) {
                        pacienteIdField.value = '';
                        console.log('Campo paciente_id resetado');
                    }
                    var hiddenPacienteIdField = mainForm.querySelector('[name=hidden_paciente_id]');
                    if (hiddenPacienteIdField) {
                        hiddenPacienteIdField.value = '';
                        console.log('Campo hidden_paciente_id resetado');
                    }
                    console.log('Formulário principal reexibido e reiniciado');
                } else {
                    console.error('Formulário principal não encontrado');
                    __adianti_message('0007 - Erro: Formulário principal não encontrado.', function() {});
                }
            } catch (e) {
                console.error('Erro em leaveJitsiMeeting:', e);
                __adianti_toast('error', '0008 - Erro ao sair da consulta: ' + e.message, '', 3000);
                __adianti_load_page('index.php?class=JitsiMeetView02&method=showMainForm');
            }
        };

        window.openObservationForm = function() {
            console.log('Iniciando openObservationForm');
            try {
                var mainForm = document.forms['form_JitsiMeetView02'];
                console.log('Formulário encontrado:', !!mainForm);
                var pacienteId = mainForm ? (mainForm.querySelector('[name=hidden_paciente_id]')?.value || mainForm.querySelector('[name=paciente_id]').value) : null;
                var meetingData = JSON.parse('{$this->getMeetingDataJson()}');
                console.log('paciente_id do formulário:', pacienteId);
                console.log('meetingData:', meetingData);

                if (!pacienteId || pacienteId === '') {
                    pacienteId = meetingData.paciente_id || null;
                    console.log('paciente_id obtido de meetingData:', pacienteId);
                }

                if (!pacienteId || pacienteId === '') {
                    console.log('Tentando recuperar paciente_id do log de videoconferência');
                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', 'index.php?class=JitsiMeetView02&method=getPacienteIdFromLog', false);
                    xhr.setRequestHeader('Content-Type', 'application/json');
                    xhr.send(JSON.stringify({ room_name: meetingData.room_name }));
                    var response = JSON.parse(xhr.responseText);
                    console.log('Resposta do getPacienteIdFromLog:', response);
                    if (response.status === 'success' && response.paciente_id) {
                        pacienteId = response.paciente_id;
                        console.log('paciente_id obtido do log:', pacienteId);
                    }
                }

                if (!pacienteId || pacienteId === '') {
                    console.error('paciente_id não definido');
                    __adianti_message('0009 - Erro: Nenhum paciente selecionado.', function() {});
                    return;
                }

                var url = 'index.php?class=PessoaObservacaoFormLog&paciente_id=' + encodeURIComponent(pacienteId);
                console.log('Carregando formulário de observação com URL:', url);
                if (typeof __adianti_load_page === 'undefined') {
                    console.error('__adianti_load_page não definida');
                    __adianti_message('0009 - Erro: Função de navegação não disponível.', function() {});
                    return;
                }
                console.log('Chamando __adianti_load_page com URL:', url);
                __adianti_load_page(url);
                window.scrollTo(0, 0);
            } catch (e) {
                console.error('Erro em openObservationForm:', e);
                __adianti_message('0009 - Erro ao abrir formulário de observação: ' + e.message, function() {});
            }
        };
JS;
        TScript::create($script);
    }

    private function getMeetingDataJson()
    {
        $meeting_data = TSession::getValue('meeting_data');
        if (!$meeting_data) {
            TTransaction::log('meeting_data não encontrado na sessão');
            $meeting_data = [
                'paciente_id' => '',
                'paciente_nome' => 'Paciente Desconhecido',
                'empresa_id' => '',
                'empresa_nome' => 'Empresa Desconhecida',
                'profissional_id' => '',
                'profissional_nome' => 'Profissional Desconhecido',
                'pessoa_id' => '',
                'room_name' => ''
            ];
        } elseif (empty($meeting_data['paciente_id'])) {
            TTransaction::log('paciente_id ausente em meeting_data: ' . json_encode($meeting_data));
        }
        TTransaction::log('meeting_data retornado: ' . json_encode($meeting_data));
        return json_encode($meeting_data);
    }

    public function joinMeeting($param)
    {
        TTransaction::log('Iniciando joinMeeting com param: ' . json_encode($param));
        try {
            TTransaction::open('bdgestorweb');
            TTransaction::log('Conexão com bdgestorweb aberta com sucesso');

            $data = $this->form->getData();
            TTransaction::log('Dados do formulário: ' . json_encode($data));

            $room_name = strtoupper(trim($data->room_name));
            $user_name = trim($data->user_name);
            $paciente_id = $data->paciente_id;
            $empresa_id = $data->empresa_id;
            $participant_type = isset($param['participant_type']) ? $param['participant_type'] : 'profissional';

            TTransaction::log('joinMeeting data: ' . json_encode([
                'room_name' => $room_name,
                'user_name' => $user_name,
                'paciente_id' => $paciente_id,
                'empresa_id' => $empresa_id,
                'participant_type' => $participant_type,
                'param' => $param
            ]));

            if (empty($room_name) || empty($user_name) || empty($paciente_id) || empty($empresa_id)) {
                throw new Exception('Todos os campos são obrigatórios!');
            }

            if (!self::validateRoomName($room_name)) {
                throw new Exception('Nome da sala inválido. Use letras, números, hífens ou underscores.');
            }

            $data->hidden_paciente_id = $paciente_id;
            $this->form->setData($data);
            TTransaction::log('hidden_paciente_id definido no formulário: ' . $paciente_id);

            $log = new LogVideoconferencia();
            $log->empresa_id = $empresa_id;
            $log->profissional_id = ($participant_type === 'profissional') ? $user_name : null;
            $log->pessoa_id = ($participant_type === 'paciente') ? $user_name : $paciente_id;
            $log->nome_sala = $room_name;
            $log->datainicial_conexao = date('Y-m-d');
            $log->horainicial_conexao = date('H:i:s');
            $log->store();
            TSession::setValue('log_videoconferencia_id_' . $participant_type, $log->id);
            TSession::setValue('log_videoconferencia_id_profissional_' . $user_name, $log->id);
            TTransaction::log('Log de videoconferência salvo com ID: ' . $log->id . ' para ' . $participant_type);

            $patient_link = "index.php?class=JitsiMeetView02&method=joinPatientMeeting&room_name={$room_name}&participant_type=paciente&user_name={$paciente_id}";
            TSession::setValue('patient_link', $patient_link);
            TTransaction::log('Link do paciente gerado: ' . $patient_link);

            TSession::setValue('participant_mapping_' . $room_name, [
                'profissional_id' => $user_name,
                'profissional_jitsi_id' => null,
                'paciente_id' => $paciente_id,
                'paciente_jitsi_id' => null
            ]);

            if (class_exists('SystemUsers') && $participant_type === 'profissional') {
                $user = SystemUsers::find($user_name);
                $user_display_name = $user ? $user->name : $user_name;
                $profissional_nome = $user ? $user->name : 'Profissional Desconhecido';
            } elseif (class_exists('Pessoa') && $participant_type === 'paciente') {
                $user = Pessoa::find($user_name);
                $user_display_name = $user ? $user->nome : $user_name;
                $profissional_nome = 'Paciente';
            } else {
                throw new Exception('Classe de usuário não encontrada.');
            }

            $tempo_atendimento = 'Não informado';
            if (class_exists('Pessoa') && $participant_type === 'profissional') {
                $profissional_pessoa = Pessoa::where('usuario_sistema', '=', $user_name)->first();
                $tempo_atendimento = $profissional_pessoa && !empty($profissional_pessoa->tempo_atendimento) 
                    ? htmlspecialchars($profissional_pessoa->tempo_atendimento, ENT_QUOTES, 'UTF-8') 
                    : 'Não informado';
            }
            TTransaction::log('tempo_atendimento obtido: ' . $tempo_atendimento);

            $vhorariofinal = '';
            if ($log->horainicial_conexao && $tempo_atendimento !== 'Não informado') {
                try {
                    $data_inicio = new DateTime($log->datainicial_conexao . ' ' . $log->horainicial_conexao);
                    $minutos = (int)$tempo_atendimento;
                    $data_inicio->modify("+{$minutos} minutes");
                    $vhorariofinal = htmlspecialchars($data_inicio->format('H:i:s'), ENT_QUOTES, 'UTF-8');
                    TTransaction::log('vhorariofinal calculado: ' . $vhorariofinal);
                } catch (Exception $e) {
                    TTransaction::log('Erro ao calcular vhorariofinal: ' . $e->getMessage());
                    $vhorariofinal = 'Não calculado';
                }
            } else {
                $vhorariofinal = 'Não calculado';
                TTransaction::log('vhorariofinal não calculado: horainicial_conexao ou tempo_atendimento ausente');
            }

            if (class_exists('Pessoa')) {
                $pessoa = Pessoa::find($paciente_id);
                $paciente_nome = $pessoa ? $pessoa->nome : 'Paciente Desconhecido';
            } else {
                throw new Exception('Classe Pessoa não encontrada.');
            }

            if (class_exists('SystemUnit')) {
                $empresa = SystemUnit::find($empresa_id);
                $empresa_nome = $empresa ? $empresa->name : 'Empresa Desconhecida';
            } else {
                throw new Exception('Classe SystemUnit não encontrada.');
            }

            $room_name = htmlspecialchars($room_name, ENT_QUOTES, 'UTF-8');
            $user_display_name = htmlspecialchars($user_display_name, ENT_QUOTES, 'UTF-8');
            $paciente_nome = htmlspecialchars($paciente_nome, ENT_QUOTES, 'UTF-8');
            $paciente_id = htmlspecialchars($paciente_id, ENT_QUOTES, 'UTF-8');
            $patient_link = htmlspecialchars($patient_link, ENT_QUOTES, 'UTF-8');
            $horainicial_conexao = htmlspecialchars($log->horainicial_conexao, ENT_QUOTES, 'UTF-8');
            $profissional_nome = htmlspecialchars($profissional_nome, ENT_QUOTES, 'UTF-8');

            TSession::setValue('meeting_data', [
                'paciente_id' => $paciente_id,
                'paciente_nome' => $paciente_nome,
                'empresa_id' => $empresa_id,
                'empresa_nome' => $empresa_nome,
                'profissional_id' => ($participant_type === 'profissional') ? $user_name : null,
                'profissional_nome' => $profissional_nome,
                'pessoa_id' => ($participant_type === 'paciente') ? $user_name : $paciente_id,
                'room_name' => $room_name
            ]);
            TTransaction::log('meeting_data salvo: ' . json_encode(TSession::getValue('meeting_data')));

            $script = <<<JS
                console.log('Iniciando script de videoconferência com roomName: {$room_name}, userDisplayName: {$user_display_name}, participante: {$participant_type}');
                function initializeJitsi() {
                    console.log('Iniciando initializeJitsi com roomName: {$room_name}, userDisplayName: {$user_display_name}');
                    try {
                        if (typeof JitsiMeetExternalAPI === 'undefined') {
                            console.error('JitsiMeetExternalAPI não definida');
                            __adianti_message('0000 - Erro: Biblioteca Jitsi não carregada. Verifique sua conexão e tente novamente.', function() {});
                            return false;
                        }

                        if (window.jitsiApi) {
                            try {
                                window.jitsiApi.executeCommand('hangup');
                                window.jitsiApi.dispose();
                                console.log('Jitsi API anterior finalizada');
                            } catch (e) {
                                console.error('Erro ao finalizar Jitsi API anterior:', e);
                            }
                            window.jitsiApi = null;
                        }

                        var domain = "meet.jit.si";
                        var options = {
                            roomName: "{$room_name}",
                            width: "100%",
                            height: "100%",
                            parentNode: document.querySelector("#jitsi-container"),
                            userInfo: {
                                displayName: "{$user_display_name}"
                            },
                            configOverwrite: {
                                startWithAudioMuted: false,
                                startWithVideoMuted: false,
                                enableWelcomePage: false,
                                prejoinPageEnabled: false,
                                disableDeepLinking: true
                            },
                            interfaceConfigOverwrite: {
                                SHOW_JITSI_WATERMARK: false,
                                TOOLBAR_BUTTONS: [
                                    'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
                                    'fodeviceselection', 'hangup', 'profile', 'chat', 'settings',
                                    'raisehand', 'videoquality', 'filmstrip', 'invite', 'tileview'
                                ]
                            }
                        };

                        console.log('Inicializando JitsiMeetExternalAPI com opções:', JSON.stringify(options));
                        window.jitsiApi = new JitsiMeetExternalAPI(domain, options);
                        window.participantType = '{$participant_type}';
                        window.participantId = '{$user_name}';
                        window.jitsiApi.addEventListener('readyToClose', function() {
                            console.log('Evento readyToClose disparado para {$participant_type}, ID: {$user_name}');
                            window.leaveJitsiMeeting('{$participant_type}', '{$user_name}');
                        });
                        window.jitsiApi.addEventListener('participantJoined', function(event) {
                            console.log('Participante entrou:', JSON.stringify(event));
                            var xhr = new XMLHttpRequest();
                            xhr.open('POST', 'index.php?class=02&method=onParticipantJoined', false);
                            xhr.setRequestHeader('Content-Type', 'application/json');
                            xhr.send(JSON.stringify({
                                room_name: '{$room_name}',
                                participant_id: event.id,
                                participant_name: event.displayName,
                                pessoa_id: '{$user_name}'
                            }));
                            console.log('Log de entrada enviado, resposta:', xhr.responseText);
                        });
                        window.jitsiApi.addEventListener('participantLeft', function(event) {
                            console.log('Participante saiu:', JSON.stringify(event));
                            var xhr = new XMLHttpRequest();
                            xhr.open('POST', 'index.php?class=JitsiMeetView02&method=onParticipantLeft', false);
                            xhr.setRequestHeader('Content-Type', 'application/json');
                            xhr.send(JSON.stringify({
                                room_name: '{$room_name}',
                                participant_id: event.id,
                                participant_name: event.displayName
                            }));
                            console.log('Log de saída enviado, resposta:', xhr.responseText);
                        });

                        var jitsiContainer = document.getElementById("jitsi-container");
                        if (jitsiContainer) {
                            jitsiContainer.style.display = "block";
                            console.log('jitsi-container visível');
                        } else {
                            console.error('jitsi-container não encontrado');
                            __adianti_message('0014 - Erro: Contêiner do Jitsi não encontrado.', function() {});
                            return false;
                        }
                        var btnLeave = document.getElementById("btn-leave");
                        if (btnLeave) {
                            btnLeave.style.display = "inline-block";
                            console.log('Botão Sair visível:', btnLeave.style.display);
                        }
                        var btnObservation = document.getElementById("btn-observation");
                        if (btnObservation) {
                            btnObservation.style.display = "inline-block";
                            console.log('Botão Observação visível:', btnObservation.style.display);
                        }
                        var mainForm = document.forms["form_JitsiMeetView02"];
                        if (mainForm) {
                            mainForm.style.display = "none";
                            console.log('Formulário principal ocultado');
                        } else {
                            console.error('Formulário principal não encontrado');
                            __adianti_message('0014 - Erro: Formulário principal não encontrado.', function() {});
                        }
                        var infoContainer = document.getElementById("info-container");
                        if (infoContainer) {
                            infoContainer.style.display = "block";
                            document.getElementById("display-room-name").textContent = "{$room_name}";
                            document.getElementById("display-paciente-codigo").textContent = "{$paciente_id}";
                            document.getElementById("display-paciente-nome").textContent = "{$paciente_nome}";
                            document.getElementById("display-horainicial-conexao").textContent = "{$horainicial_conexao}";
                            document.getElementById("display-vhorariofinal").textContent = "{$vhorariofinal}";
                            document.getElementById("display-profissional-nome").textContent = "{$profissional_nome}";
                            document.getElementById("display-tempo-atendimento").textContent = "{$tempo_atendimento}";
                            document.getElementById("display-patient-link").innerHTML = '<a href="{$patient_link}" target="_blank">{$patient_link}</a>';
                            console.log('info-container exibido com roomName: {$room_name}, pacienteCodigo: {$paciente_id}, pacienteNome: {$paciente_nome}, profissionalNome: {$profissional_nome}, tempoAtendimento: {$tempo_atendimento}, horainicialConexao: {$horainicial_conexao}, vhorariofinal: {$vhorariofinal}, patientLink: {$patient_link}');
                        } else {
                            console.error('info-container não encontrado');
                            __adianti_message('0015 - Erro: Contêiner de informações não encontrado.', function() {});
                        }
                        console.log('Jitsi inicializado com sucesso');
                        return true;
                    } catch (e) {
                        console.error('Erro ao inicializar Jitsi:', e);
                        __adianti_message('0001 - Erro ao iniciar a videoconferência: ' + e.message, function() {});
                        return false;
                    }
                }

                if (typeof JitsiMeetExternalAPI !== 'undefined') {
                    console.log('JitsiMeetExternalAPI já carregada, iniciando videoconferência');
                    initializeJitsi();
                } else {
                    console.log('Carregando JitsiMeetExternalAPI');
                    var script = document.createElement("script");
                    script.src = "https://meet.jit.si/external_api.js";
                    script.async = true;
                    script.onload = function() {
                        console.log('JitsiMeetExternalAPI carregada com sucesso, iniciando videoconferência');
                        initializeJitsi();
                    };
                    script.onerror = function() {
                        console.error('Erro ao carregar JitsiMeetExternalAPI');
                        __adianti_message('0002 - Erro: Não foi possível carregar a biblioteca Jitsi. Verifique sua conexão e tente novamente.', function() {});
                    };
                    document.head.appendChild(script);

                    var checkJitsiLoaded = setInterval(function() {
                        if (typeof JitsiMeetExternalAPI !== 'undefined') {
                            console.log('JitsiMeetExternalAPI detectada, iniciando videoconferência');
                            clearInterval(checkJitsiLoaded);
                            initializeJitsi();
                        }
                    }, 100);
                    setTimeout(function() {
                        if (typeof JitsiMeetExternalAPI === 'undefined') {
                            clearInterval(checkJitsiLoaded);
                            console.error('JitsiMeetExternalAPI não carregou em 30 segundos');
                            __adianti_message('0002 - Erro: Não foi possível carregar a biblioteca Jitsi após 30 segundos. Verifique sua conexão.', function() {});
                        }
                    }, 30000);
                }
JS;

            TScript::create($script);
            TTransaction::close();
            TTransaction::log('joinMeeting concluído com sucesso');
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em joinMeeting: ' . $e->getMessage());
            new TMessage('error', '0003 - Erro ao processar a requisição: ' . $e->getMessage());
            if (isset($data)) {
                $this->form->setData($data);
            }
        }
    }

    public function joinPatientMeeting($param)
    {
        TTransaction::log('Iniciando joinPatientMeeting com param: ' . json_encode($param));
        try {
            TTransaction::open('bdgestorweb');

            $room_name = strtoupper(trim($param['room_name']));
            $user_name = trim($param['user_name']);
            $participant_type = 'paciente';

            if (empty($room_name) || empty($user_name)) {
                throw new Exception('Nome da sala e ID do paciente são obrigatórios!');
            }

            if (!self::validateRoomName($room_name)) {
                throw new Exception('Nome da sala inválido. Use letras, números, hífens ou underscores.');
            }

            $pessoa = Pessoa::find($user_name);
            if (!$pessoa) {
                throw new Exception('Paciente não encontrado.');
            }

            $log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                      ->where('datafim_conexao', '=', null)
                                      ->where('profissional_id', '!=', null)
                                      ->first();
            if (!$log) {
                throw new Exception('Sala de consulta não encontrada ou já encerrada.');
            }

            if ($log->pessoa_id != $user_name) {
                throw new Exception('Paciente não autorizado para esta sala.');
            }

            $existing_log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                              ->where('pessoa_id', '=', $user_name)
                                              ->where('profissional_id', '=', null)
                                              ->where('datafim_conexao', '=', null)
                                              ->first();
            if (!$existing_log) {
                $patient_log = new LogVideoconferencia();
                $patient_log->empresa_id = $log->empresa_id;
                $patient_log->profissional_id = null;
                $patient_log->pessoa_id = $user_name;
                $patient_log->nome_sala = $room_name;
                $patient_log->datainicial_conexao = date('Y-m-d');
                $patient_log->horainicial_conexao = date('H:i:s');
                $patient_log->store();
                TSession::setValue('log_videoconferencia_id_paciente_' . $user_name, $patient_log->id);
                TTransaction::log('Log de entrada do paciente salvo com ID: ' . $patient_log->id);
            } else {
                TSession::setValue('log_videoconferencia_id_paciente_' . $user_name, $existing_log->id);
                TTransaction::log('Log de entrada do paciente já existe com ID: ' . $existing_log->id);
            }

            $user_display_name = htmlspecialchars($pessoa->nome, ENT_QUOTES, 'UTF-8');
            $paciente_nome = $user_display_name;
            $paciente_id = $user_name;
            $empresa_id = $log->empresa_id;

            $participant_mapping = TSession::getValue('participant_mapping_' . $room_name) ?: [];
            $participant_mapping['paciente_id'] = $user_name;
            TSession::setValue('participant_mapping_' . $room_name, $participant_mapping);

            $script = <<<JS
                console.log('Iniciando script de videoconferência para paciente com roomName: {$room_name}, userDisplayName: {$user_display_name}');
                function initializeJitsi() {
                    console.log('Iniciando initializeJitsi com roomName: {$room_name}, userDisplayName: {$user_display_name}');
                    try {
                        if (typeof JitsiMeetExternalAPI === 'undefined') {
                            console.error('JitsiMeetExternalAPI não definida');
                            alert('Erro: Biblioteca Jitsi não carregada. Verifique sua conexão e tente novamente.');
                            return false;
                        }

                        var domain = "meet.jit.si";
                        var options = {
                            roomName: "{$room_name}",
                            width: "100%",
                            height: "100%",
                            parentNode: document.body,
                            userInfo: {
                                displayName: "{$user_display_name}"
                            },
                            configOverwrite: {
                                startWithAudioMuted: true,
                                startWithVideoMuted: true,
                                enableWelcomePage: false,
                                prejoinPageEnabled: false,
                                disableDeepLinking: true
                            },
                            interfaceConfigOverwrite: {
                                SHOW_JITSI_WATERMARK: false,
                                TOOLBAR_BUTTONS: [
                                    'microphone', 'camera', 'closedcaptions', 'fullscreen',
                                    'fodeviceselection', 'hangup', 'profile', 'chat',
                                    'settings', 'raisehand', 'videoquality', 'filmstrip',
                                    'invite', 'tileview'
                                ]
                            }
                        };

                        console.log('Inicializando JitsiMeetExternalAPI com opções:', JSON.stringify(options));
                        window.jitsiApi = new JitsiMeetExternalAPI(domain, options);
                        window.participantType = '{$participant_type}';
                        window.participantId = '{$user_name}';
                        window.jitsiApi.addEventListener('readyToClose', function() {
                            console.log('Evento readyToClose disparado para {$participant_type}, ID: {$user_name}');
                            window.leaveJitsiMeeting('{$participant_type}', '{$user_name}');
                        });
                        window.jitsiApi.addEventListener('participantJoined', function(event) {
                            console.log('Participante entrou:', JSON.stringify(event));
                            var xhr = new XMLHttpRequest();
                            xhr.open('POST', 'index.php?class=JitsiMeetView02&method=onParticipantJoined', false);
                            xhr.setRequestHeader('Content-Type', 'application/json');
                            xhr.send(JSON.stringify({
                                room_name: '{$room_name}',
                                participant_id: event.id,
                                participant_name: event.displayName,
                                pessoa_id: '{$user_name}'
                            }));
                            console.log('Log de entrada enviado, resposta:', xhr.responseText);
                        });
                        window.jitsiApi.addEventListener('participantLeft', function(event) {
                            console.log('Participante saiu:', JSON.stringify(event));
                            var xhr = new XMLHttpRequest();
                            xhr.open('POST', 'index.php?class=JitsiMeetView02&method=onParticipantLeft', false);
                            xhr.setRequestHeader('Content-Type', 'application/json');
                            xhr.send(JSON.stringify({
                                room_name: '{$room_name}',
                                participant_id: event.id,
                                participant_name: event.displayName
                            }));
                            console.log('Log de saída enviado, resposta:', xhr.responseText);
                        });
                        console.log('Jitsi inicializado com sucesso para paciente');
                        return true;
                    } catch (e) {
                        console.error('Erro ao inicializar Jitsi:', e);
                        alert('Erro ao iniciar a videoconferência: ' + e.message);
                        return false;
                    }
                }

                if (typeof JitsiMeetExternalAPI !== 'undefined') {
                    console.log('JitsiMeetExternalAPI já carregada, iniciando videoconferência');
                    initializeJitsi();
                } else {
                    console.log('Carregando JitsiMeetExternalAPI');
                    var script = document.createElement("script");
                    script.src = "https://meet.jit.si/external_api.js";
                    script.async = true;
                    script.onload = function() {
                        console.log('JitsiMeetExternalAPI carregada com sucesso, iniciando videoconferência');
                        initializeJitsi();
                    };
                    script.onerror = function() {
                        console.error('Erro ao carregar JitsiMeetExternalAPI');
                        alert('Erro ao carregar a biblioteca Jitsi. Verifique sua conexão e tente novamente mais tarde.');
                    };
                    document.head.appendChild(script);

                    var checkJitsiLoaded = setInterval(function() {
                        if (typeof JitsiMeetExternalAPI !== 'undefined') {
                            console.log('JitsiMeetExternalAPI detectada, iniciando videoconferência');
                            clearInterval(checkJitsiLoaded);
                            initializeJitsi();
                        }
                    }, 100);
                    setTimeout(function() {
                        if (typeof JitsiMeetExternalAPI === 'undefined') {
                            clearInterval(checkJitsiLoaded);
                            console.error('JitsiMeetExternalAPI não carregou em 30 segundos');
                            alert('Erro: Não foi possível carregar a biblioteca Jitsi após 30 segundos. Verifique sua conexão.');
                        }
                    }, 30000);
                }
JS;

            TScript::create($script);
            TTransaction::close();
            TTransaction::log('joinPatientMeeting concluído com sucesso');
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em joinPatientMeeting: ' . $e->getMessage());
            echo "<script>alert('Erro ao processar a requisição: " . addslashes($e->getMessage()) . "');</script>";
        }
    }

    public static function getPacienteIdFromLog($param)
    {
        TTransaction::log('Iniciando getPacienteIdFromLog com param: ' . json_encode($param));
        try {
            TTransaction::open('bdgestorweb');

            $room_name = $param['room_name'] ?? null;
            if (!$room_name) {
                TTransaction::log('room_name não fornecido');
                echo json_encode(['status' => 'error', 'message' => 'Nome da sala não fornecido']);
                TTransaction::close();
                return;
            }

            $log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                     ->where('datafim_conexao', '=', null)
                                     ->where('profissional_id', '!=', null)
                                     ->first();
            if ($log && $log->pessoa_id) {
                TTransaction::log('paciente_id encontrado no log: ' . $log->pessoa_id);
                echo json_encode(['status' => 'success', 'paciente_id' => $log->pessoa_id]);
            } else {
                TTransaction::log('Nenhum log ativo encontrado para room_name: ' . $room_name);
                echo json_encode(['status' => 'error', 'message' => 'Nenhum log ativo encontrado']);
            }

            TTransaction::close();
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em getPacienteIdFromLog: ' . $e->getMessage());
            echo json_encode(['status' => 'error', 'message' => 'Erro ao recuperar paciente_id: ' . $e->getMessage()]);
        }
    }

    public static function onParticipantJoined($param)
    {
        TTransaction::log('Iniciando onParticipantJoined com param: ' . json_encode($param));
        try {
            TTransaction::open('bdgestorweb');

            $room_name = $param['room_name'];
            $participant_id = $param['participant_id'];
            $participant_name = $param['participant_name'];
            $pessoa_id = $param['pessoa_id'] ?? null;

            $participant_mapping = TSession::getValue('participant_mapping_' . $room_name);
            if (!$participant_mapping) {
                TTransaction::log('Mapeamento de participantes não encontrado para room_name: ' . $room_name);
                echo json_encode(['status' => 'error', 'message' => 'Mapeamento de participantes não encontrado']);
                TTransaction::close();
                return;
            }

            $pessoa = null;
            if ($pessoa_id) {
                $pessoa = Pessoa::find($pessoa_id);
            }
            if (!$pessoa && $participant_name) {
                $pessoa = Pessoa::where('nome', '=', $participant_name)->first();
            }
            if (!$pessoa) {
                TTransaction::log('Nenhum Pessoa encontrado para participant_name: ' . $participant_name . ', pessoa_id: ' . ($pessoa_id ?: 'N/A'));
                echo json_encode(['status' => 'error', 'message' => 'Participante não identificado']);
                TTransaction::close();
                return;
            }

            if ($pessoa->id == $participant_mapping['paciente_id']) {
                $participant_mapping['paciente_jitsi_id'] = $participant_id;
                TSession::setValue('participant_mapping_' . $room_name, $participant_mapping);
                TTransaction::log('Mapeamento atualizado: paciente_jitsi_id = ' . $participant_id);
            } elseif ($pessoa->id == $participant_mapping['profissional_id']) {
                $participant_mapping['profissional_jitsi_id'] = $participant_id;
                TSession::setValue('participant_mapping_' . $room_name, $participant_mapping);
                TTransaction::log('Mapeamento atualizado: profissional_jitsi_id = ' . $participant_id);
            }

            $existing_log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                              ->where('pessoa_id', '=', $pessoa->id)
                                              ->where('datafim_conexao', '=', null)
                                              ->first();
            if ($existing_log) {
                TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa->id, $existing_log->id);
                TTransaction::log('Log já existe para pessoa_id: ' . $pessoa->id . ', ID: ' . $existing_log->id);
                echo json_encode(['status' => 'success', 'message' => 'Log já existe', 'log_id' => $existing_log->id]);
                TTransaction::close();
                return;
            }

            $log = new LogVideoconferencia();
            $log->empresa_id = TSession::getValue('meeting_data')['empresa_id'] ?? $existing_log->empresa_id ?? null;
            $log->profissional_id = ($pessoa->id == $participant_mapping['profissional_id']) ? $pessoa->id : null;
            $log->pessoa_id = $pessoa->id;
            $log->nome_sala = $room_name;
            $log->datainicial_conexao = date('Y-m-d');
            $log->horainicial_conexao = date('H:i:s');
            $log->store();
            TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa->id, $log->id);
            if ($pessoa->id == $participant_mapping['profissional_id']) {
                TSession::setValue('log_videoconferencia_id_profissional', $log->id);
            }
            TTransaction::log('Log de videoconferência salvo com ID: ' . $log->id . ' para pessoa_id: ' . $pessoa->id);
            echo json_encode(['status' => 'success', 'message' => 'Log salvo', 'log_id' => $log->id]);

            TTransaction::close();
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em onParticipantJoined: ' . $e->getMessage());
            echo json_encode(['status' => 'error', 'message' => 'Erro ao processar entrada do participante: ' . $e->getMessage()]);
        }
    }

    public static function onParticipantLeft($param)
    {
        TTransaction::log('Iniciando onParticipantLeft com param: ' . json_encode($param));
        try {
            TTransaction::open('bdgestorweb');

            $room_name = $param['room_name'];
            $participant_id = $param['participant_id'];
            $participant_name = $param['participant_name'];

            $participant_mapping = TSession::getValue('participant_mapping_' . $room_name);
            if (!$participant_mapping) {
                TTransaction::log('Mapeamento de participantes não encontrado para room_name: ' . $room_name);
                echo json_encode(['status' => 'error', 'message' => 'Mapeamento de participantes não encontrado']);
                TTransaction::close();
                return;
            }

            $pessoa_id = null;
            if ($participant_id == $participant_mapping['profissional_jitsi_id']) {
                $pessoa_id = $participant_mapping['profissional_id'];
            } elseif ($participant_id == $participant_mapping['paciente_jitsi_id']) {
                $pessoa_id = $participant_mapping['paciente_id'];
            }

            if (!$pessoa_id) {
                TTransaction::log('Nenhum pessoa_id identificado para participant_id: ' . $participant_id);
                echo json_encode(['status' => 'error', 'message' => 'Participante não identificado']);
                TTransaction::close();
                return;
            }

            $log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                     ->where('pessoa_id', '=', $pessoa_id)
                                     ->where('datafim_conexao', '=', null)
                                     ->first();
            if ($log) {
                $log->datafim_conexao = date('Y-m-d');
                $log->horafim_conexao = date('H:i:s');
                $log->store();
                TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa_id, null);
                if ($pessoa_id == $participant_mapping['profissional_id']) {
                    TSession::setValue('log_videoconferencia_id_profissional', null);
                }
                TTransaction::log('Log de saída atualizado para pessoa_id: ' . $pessoa_id . ', log_id: ' . $log->id);
                echo json_encode(['status' => 'success', 'message' => 'Log de saída atualizado', 'log_id' => $log->id]);
            } else {
                TTransaction::log('Nenhum log ativo encontrado para pessoa_id: ' . $pessoa_id . ', room_name: ' . $room_name);
                echo json_encode(['status' => 'error', 'message' => 'Nenhum log ativo encontrado']);
            }

            TTransaction::close();
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em onParticipantLeft: ' . $e->getMessage());
            echo json_encode(['status' => 'error', 'message' => 'Erro ao processar saída do participante: ' . $e->getMessage()]);
        }
    }

    public static function onLeaveMeeting($param)
    {
        TTransaction::log('Iniciando onLeaveMeeting com param: ' . json_encode($param));
        try {
            TTransaction::open('bdgestorweb');

            $participant_type = $param['participant_type'] ?? null;
            $pessoa_id = $param['pessoa_id'] ?? null;
            $meeting_data = TSession::getValue('meeting_data');
            $room_name = $meeting_data['room_name'] ?? null;

            if (!$room_name || !$pessoa_id || !$participant_type) {
                TTransaction::log('Parâmetros insuficientes em onLeaveMeeting: room_name=' . ($room_name ?: 'N/A') . ', pessoa_id=' . ($pessoa_id ?: 'N/A') . ', participant_type=' . ($participant_type ?: 'N/A'));
                echo json_encode(['status' => 'error', 'message' => 'Parâmetros insuficientes']);
                TTransaction::close();
                return;
            }

            $log_id = TSession::getValue('log_videoconferencia_id_' . $participant_type);
            if (!$log_id && $participant_type === 'paciente') {
                $log_id = TSession::getValue('log_videoconferencia_id_paciente_' . $pessoa_id);
            }

            if ($log_id) {
                $log = LogVideoconferencia::find($log_id);
                if ($log && !$log->datafim_conexao) {
                    $log->datafim_conexao = date('Y-m-d');
                    $log->horafim_conexao = date('H:i:s');
                    $log->store();
                    TSession::setValue('log_videoconferencia_id_' . $participant_type, null);
                    if ($participant_type === 'paciente') {
                        TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa_id, null);
                    }
                    TTransaction::log('Log de saída atualizado em onLeaveMeeting para log_id: ' . $log_id . ', pessoa_id: ' . $pessoa_id);
                    echo json_encode(['status' => 'success', 'message' => 'Log de saída atualizado', 'log_id' => $log_id]);
                } else {
                    TTransaction::log('Log não encontrado ou já encerrado para log_id: ' . $log_id);
                    echo json_encode(['status' => 'error', 'message' => 'Log não encontrado ou já encerrado']);
                }
            } else {
                $log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                         ->where('pessoa_id', '=', $pessoa_id)
                                         ->where('datafim_conexao', '=', null)
                                         ->first();
                if ($log) {
                    $log->datafim_conexao = date('Y-m-d');
                    $log->horafim_conexao = date('H:i:s');
                    $log->store();
                    TSession::setValue('log_videoconferencia_id_' . $participant_type, null);
                    if ($participant_type === 'paciente') {
                        TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa_id, null);
                    }
                    TTransaction::log('Log de saída atualizado em onLeaveMeeting para pessoa_id: ' . $pessoa_id);
                    echo json_encode(['status' => 'success', 'message' => 'Log de saída atualizado', 'log_id' => $log->id]);
                } else {
                    TTransaction::log('Nenhum log ativo encontrado para pessoa_id: ' . $pessoa_id . ', room_name: ' . $room_name);
                    echo json_encode(['status' => 'error', 'message' => 'Nenhum log ativo encontrado']);
                }
            }

            TTransaction::close();
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em onLeaveMeeting: ' . $e->getMessage());
            echo json_encode(['status' => 'error', 'message' => 'Erro ao processar saída: ' . $e->getMessage()]);
        }
    }

    public static function validateRoomName($room_name)
    {
        return preg_match('/^[a-zA-Z0-9_-]+$/', $room_name);
    }

    public function onLoad($param)
    {
        TTransaction::log('Iniciando onLoad com param: ' . json_encode($param));
        $paciente_id = TSession::getValue('paciente_id');

        if (!empty($paciente_id)) {
            //new TMessage('info', 'Paciente ID recuperado da sessão: ' . $paciente_id);
        } else {
            new TMessage('info', 'Sessão em JitsiMeetView02: ' . print_r($_SESSION, true));
            throw new Exception('Paciente ID não encontrado na sessão. Por favor, tente novamente.');
        }

        try {
            $data = new stdClass();
            $data->room_name = $this->generateRoomName();
            $data->user_name = TSession::getValue('userid');
            $data->empresa_id = TSession::getValue('userunitid');
            $data->paciente_id = TSession::getValue('paciente_id');
            $data->hidden_paciente_id = null;
            $this->form->setData($data);
            TTransaction::log('Formulário inicializado em onLoad com dados: ' . json_encode($data));
        } catch (Exception $e) {
            TTransaction::log('Erro em onLoad: ' . $e->getMessage());
            new TMessage('error', '0016 - Erro ao carregar a página: ' . $e->getMessage());
        }
    }

    public static function onExitMeeting($param)
    {
        TTransaction::log('Iniciando onExitMeeting com param: ' . json_encode($param));
        try {
            // Definir o fuso horário explicitamente
            date_default_timezone_set('America/Sao_Paulo');
            TTransaction::log('Fuso horário definido: ' . date_default_timezone_get());

            // Capturar data e hora atuais usando DateTime
            $now = new DateTime('now', new DateTimeZone('America/Sao_Paulo'));
            $data_fim = $now->format('Y-m-d');
            $hora_fim = $now->format('H:i:s');
            TTransaction::log('Data e hora capturadas: data_fim=' . $data_fim . ', hora_fim=' . $hora_fim);

            // Validar data e hora
            if ($data_fim === '1970-01-01' || $data_fim === '1969-12-31' || empty($data_fim) || empty($hora_fim)) {
                TTransaction::log('Erro: Data/hora inválida detectada. Usando valores de fallback.');
                $data_fim = date('Y-m-d');
                $hora_fim = date('H:i:s');
                TTransaction::log('Valores de fallback: data_fim=' . $data_fim . ', hora_fim=' . $hora_fim);
            }

            TTransaction::open('bdgestorweb');
            TTransaction::log('Transação aberta com bdgestorweb');

            // Obter parâmetros
            $participant_type = $param['participant_type'] ?? 'profissional';
            $pessoa_id = $param['pessoa_id'] ?? TSession::getValue('userid');
            $meeting_data = TSession::getValue('meeting_data');
            $room_name = $meeting_data['room_name'] ?? null;

            // Validar parâmetros
            if (!$room_name || !$pessoa_id || !$participant_type) {
                TTransaction::log('Parâmetros inválidos: room_name=' . ($room_name ?: 'N/A') . ', pessoa_id=' . ($pessoa_id ?: 'N/A') . ', participant_type=' . ($participant_type ?: 'N/A'));
                throw new Exception('Parâmetros insuficientes: room_name, pessoa_id ou participant_type não fornecidos');
            }
            TTransaction::log('Parâmetros validados: room_name=' . $room_name . ', pessoa_id=' . $pessoa_id . ', participant_type=' . $participant_type);

            // Buscar log ativo
            $log = LogVideoconferencia::where('nome_sala', '=', $room_name)
                                     ->where('pessoa_id', '=', $pessoa_id)
                                     ->where('datafim_conexao', '=', null)
                                     ->first();

            if (!$log) {
                TTransaction::log('Nenhum log ativo encontrado para pessoa_id=' . $pessoa_id . ', room_name=' . $room_name);
                throw new Exception('Nenhum log ativo encontrado para a sala e participante');
            }
            TTransaction::log('Log encontrado: ID=' . $log->id . ', pessoa_id=' . $pessoa_id . ', nome_sala=' . $room_name);

            // Atualizar log com data/hora
            $log->datafim_conexao = $data_fim;
            $log->horafim_conexao = $hora_fim;
            TTransaction::log('Antes de salvar: datafim_conexao=' . $log->datafim_conexao . ', horafim_conexao=' . $log->horafim_conexao);

            // Salvar o log
            $log->store();
            TTransaction::log('Log salvo: ID=' . $log->id . ', datafim_conexao=' . $log->datafim_conexao . ', horafim_conexao=' . $log->horafim_conexao);

            // Verificar valores salvos
            $log_verificado = LogVideoconferencia::find($log->id);
            if ($log_verificado) {
                TTransaction::log('Verificação pós-salvamento: ID=' . $log_verificado->id . ', datafim_conexao=' . ($log_verificado->datafim_conexao ?: 'N/A') . ', horafim_conexao=' . ($log_verificado->horafim_conexao ?: 'N/A'));
                if ($log_verificado->datafim_conexao !== $data_fim || $log_verificado->horafim_conexao !== $hora_fim) {
                    TTransaction::log('Erro: Valores salvos não correspondem. Esperado: data_fim=' . $data_fim . ', hora_fim=' . $hora_fim);
                }
            } else {
                TTransaction::log('Erro: Log não encontrado após salvamento, ID=' . $log->id);
            }

            // Limpar sessão
            TSession::setValue('log_videoconferencia_id_' . $participant_type, null);
            if ($participant_type === 'paciente') {
                TSession::setValue('log_videoconferencia_id_paciente_' . $pessoa_id, null);
            }
            TSession::setValue('meeting_data', null);
            TSession::setValue('patient_link', null);
            TSession::setValue('participant_mapping_' . $room_name, null);
            TTransaction::log('Sessão limpa para participant_type=' . $participant_type . ', pessoa_id=' . $pessoa_id);

            TTransaction::close();
            TTransaction::log('onExitMeeting concluído com sucesso');
            echo json_encode(['status' => 'success', 'message' => 'Saída registrada com sucesso']);
        } catch (Exception $e) {
            TTransaction::rollback();
            TTransaction::log('Erro em onExitMeeting: ' . $e->getMessage());
            echo json_encode(['status' => 'error', 'message' => 'Erro ao registrar saída: ' . $e->getMessage()]);
        }
    }
}
/*
/*
public function onEdit($param)
{
    try
    {
        if (isset($param['key']))
        {
            // Exibe mensagem de loading imediatamente
            $loadingScript = "
                // Criar overlay de loading
                if (!document.getElementById('loading-overlay')) {
                    var overlay = document.createElement('div');
                    overlay.id = 'loading-overlay';
                    overlay.style.cssText = `
                        position: fixed;
                        top: 0;
                        left: 0;
                        width: 100%;
                        height: 100%;
                        background: rgba(0, 0, 0, 0.5);
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        z-index: 9999;
                        font-family: Arial, sans-serif;
                    `;

                    var loadingBox = document.createElement('div');
                    loadingBox.style.cssText = `
                        background: white;
                        padding: 30px;
                        border-radius: 10px;
                        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
                        text-align: center;
                        min-width: 300px;
                    `;

                    var spinner = document.createElement('div');
                    spinner.style.cssText = `
                        border: 4px solid #f3f3f3;
                        border-top: 4px solid #3498db;
                        border-radius: 50%;
                        width: 40px;
                        height: 40px;
                        animation: spin 1s linear infinite;
                        margin: 0 auto 20px auto;
                    `;

                    var message = document.createElement('div');
                    message.innerHTML = '<strong>Aguarde...</strong><br>Carregando informações do agendamento';
                    message.style.cssText = 'color: #333; font-size: 16px;';

                    var progressBar = document.createElement('div');
                    progressBar.style.cssText = `
                        width: 100%;
                        height: 6px;
                        background: #f0f0f0;
                        border-radius: 3px;
                        margin-top: 15px;
                        overflow: hidden;
                    `;

                    var progress = document.createElement('div');
                    progress.style.cssText = `
                        height: 100%;
                        background: linear-gradient(90deg, #3498db, #2ecc71);
                        width: 0%;
                        transition: width 0.3s ease;
                        border-radius: 3px;
                    `;

                    progressBar.appendChild(progress);
                    loadingBox.appendChild(spinner);
                    loadingBox.appendChild(message);
                    loadingBox.appendChild(progressBar);
                    overlay.appendChild(loadingBox);
                    document.body.appendChild(overlay);

                    // Adicionar animação CSS para o spinner
                    var style = document.createElement('style');
                    style.textContent = `
                        @keyframes spin {
                            0% { transform: rotate(0deg); }
                            100% { transform: rotate(360deg); }
                        }
                    `;
                    document.head.appendChild(style);

                    // Simular progresso
                    var currentProgress = 0;
                    var progressInterval = setInterval(function() {
                        currentProgress += Math.random() * 15;
                        if (currentProgress > 90) currentProgress = 90;
                        progress.style.width = currentProgress + '%';
                    }, 100);

                    // Armazenar referência do interval para limpeza posterior
                    window.loadingProgressInterval = progressInterval;
                }
            ";

            TScript::create($loadingScript);

            $key = $param['key'];
            $startTime = microtime(true); // Início da medição de tempo

            TTransaction::open(self::$database);

            // Verifica se o registro existe antes de tentar carregá-lo
            if (!Agendamento::find($key)) 
            {
                throw new Exception('Registro não encontrado');
            }

            $object = new Agendamento($key);
            TSession::setValue('editaagenda', true);

            // Verificações de segurança antes de acessar propriedades relacionadas
            if ($object->paciente)
            {
                $object->paciente_foto = $object->paciente->foto ?? '';
                $this->paciente_foto->src = $object->paciente->caminho_foto ?? '';
                $object->paciente_tipo_atendimento = $object->paciente->tipo_atendimento ?? '';
            }

            // Configurações com verificações de existência
            if (isset($this->ContainerContaParcelasListReceber) && !empty($object->paciente_id)) {
                $this->ContainerContaParcelasListReceber->setParameter('agenda_cliente_id', $object->paciente_id);
            }

            if (isset($this->horariosesalas01) && !empty($object->profissional_id)) {
                $this->horariosesalas01->setParameter('profissional_id', $object->profissional_id);
            }

            $object->view = !empty($param['view']) ? $param['view'] : 'agendaWeek';
            $this->form->setData($object);

            // Configurações de campos com verificações
            if (!empty($object->guia_id) && $this->form->getField('guia_id'))
            {
                $this->form->getField('guia_id')->setEditable(false);
            }

            if (!empty($object->guia_idsecundaria) && $this->form->getField('guia_idsecundaria'))
            {
                $this->form->getField('guia_idsecundaria')->setEditable(false);
            }

            // Verificação de status cancelado
            if ($object->cancelado == 'S')
            {
                // Lista de botões para desabilitar
                $buttonsToDisable = [
                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                    'btn_protocoloepreparo', 'btn_gerarfinanceiro', 'btn_novopaciente',
                    'btn_usodemedicamentos', 'btn_finalizouatendimento', 'btn_marcarretorno',
                    'btn_duplicaragendamento', 'btn_cancelaragendamento', 'btn_atualizardadospaciente',
                    'btn_clique'
                ];

                foreach ($buttonsToDisable as $button) {
                    TButton::disableField(self::$formName, $button);
                }

                // Lista de campos para desabilitar
                $fieldsToDisable = 
                [
                    'empresa_id', 'origemconsulta_id', 'cobranca_id', 'guia_id','guia_idsecundaria','ordematendimento','ambiente_id',
                    'paciente_id', 'fornecedor_id', 'produto_id', 'profissional_id',
                    'solicitante', 'entrega_resultado', 'status', 'horario_inicial',
                    'horario_final', 'ordematendimento', 'ambiente_id', 'valor_original'
                ];

                foreach ($fieldsToDisable as $fieldName)
                {
                    $field = $this->form->getField($fieldName);
                    if ($field) {
                        $field->setEditable(false);
                    }
                }
            }
            else
            {
                $status = $object->status ?? '';

                if ($status == 9999 || $status == '9999') //Lista de espera
                {
                    $buttonsToDisable = [
                        'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                        'btn_protocoloepreparo', 'btn_gerarfinanceiro', 'btn_novopaciente',
                        'btn_usodemedicamentos', 'btn_finalizouatendimento', 'btn_marcarretorno',
                        'btn_duplicaragendamento', 'btn_cancelaragendamento', 'btn_atualizardadospaciente',
                        'btn_clique'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }
                }

                if ($status == '01') //Aguardando confirmação
                {
                    $fieldsToDisable =
                    [
                        'empresa_id', 'origemconsulta_id', 'cobranca_id',
                        'paciente_id', 'fornecedor_id', 'produto_id', 'profissional_id',
                        'solicitante', 'entrega_resultado', 'status', 'horario_inicial',
                        'horario_final'
                    ];

                    foreach ($fieldsToDisable as $fieldName)
                    {
                        $field = $this->form->getField($fieldName);
                        if ($field) {
                            $field->setEditable(false);
                        }
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#f0ad19 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    // Lista de botões para desabilitar
                    $buttonsToDisable = [
                        'btn_remarcar', 'btn_naocompareceu', 'btn_novopaciente', 'btn_finalizouatendimento', 'btn_marcarretorno',
                        'btn_duplicaragendamento', 'btn_clique'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    TButton::enableField(self::$formName,  'btn_confirmar');
                    TButton::enableField(self::$formName,  'btn_desmarcar');
                    TButton::enableField(self::$formName,  'btn_protocoloepreparo');
                    TButton::enableField(self::$formName,  'btn_cancelaragendamento');

                    $gerouFinanceiro = $object->geroufinanceiro ?? 'N';
                    if ($gerouFinanceiro == 'N')
                    {
                        TButton::enableField(self::$formName, 'btn_gerarfinanceiro');
                    }
                    else
                    {
                        TButton::disableField(self::$formName, 'btn_gerarfinanceiro');                    
                    }
                }

                if ($status == '02')  //Confirmado
                {
                    if (method_exists($this, 'desabilitarcamposagendamento01')) {
                        self::desabilitarcamposagendamento01();
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#3C8DBC !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    // Lista de botões para desabilitar
                    $buttonsToDisable = [
                        'btn_remarcar', 'btn_novopaciente', 'btn_clique', 'btn_duplicaragendamento', 'btn_marcarretorno'
                    ];

                    foreach ($buttonsToDisable as $button)
                    {
                        TButton::disableField(self::$formName, $button);
                    }

                    TButton::enableField(self::$formName,  'btn_desmarcar');        
                    TButton::enableField(self::$formName,  'btn_remarcar');
                    TButton::enableField(self::$formName,  'btn_naocompareceu');
                    TButton::enableField(self::$formName,  'btn_protocoloepreparo');
                    TButton::enableField(self::$formName,  'btn_finalizouatendimento');

                    if (method_exists('TDateTime', 'disableField')) {
                        TDateTime::disableField(self::$formName, 'horario_inicial');
                        TDateTime::disableField(self::$formName, 'horario_final');
                    }

                    $gerouFinanceiro = $object->geroufinanceiro ?? 'N';
                    if ($gerouFinanceiro == 'N')
                    {
                        TButton::enableField(self::$formName, 'btn_gerarfinanceiro');
                    }
                    else
                    {
                        TButton::disableField(self::$formName, 'btn_gerarfinanceiro');                    
                    }
                }

                if ($status == '03')  //C:Desmarcar
                {
                    if (method_exists($this, 'desabilitarcamposagendamento01'))
                    {
                        self::desabilitarcamposagendamento01();
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#2A2D91 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    $buttonsToDisable = [
                        'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu', 
                        'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                        'btn_novopaciente', 'btn_usodemedicamentos', 'btn_clique',
                        'btn_finalizouatendimento'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    TButton::enableField(self::$formName,  'btn_duplicaragendamento');

                    if (method_exists('TDateTime', 'disableField')) {
                        TDateTime::disableField(self::$formName, 'horario_inicial');
                        TDateTime::disableField(self::$formName, 'horario_final');
                    }
                }    

                if ($status == '04') //U:Não compareceu
                {
                    if (method_exists($this, 'desabilitarcamposagendamento01'))
                    {
                        self::desabilitarcamposagendamento01();
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#2BBBAD !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    $buttonsToDisable = [
                        'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                        'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                        'btn_novopaciente', 'btn_usodemedicamentos', 'btn_clique',
                        'btn_finalizouatendimento'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    TButton::enableField(self::$formName,  'btn_duplicaragendamento');

                    if (method_exists('TDateTime', 'disableField')) {
                        TDateTime::disableField(self::$formName, 'horario_inicial');
                        TDateTime::disableField(self::$formName, 'horario_final');
                    }
                }    

                if ($status == '05') //E:Remarcar
                {
                    if (method_exists($this, 'desabilitarcamposagendamento02')) {
                        self::desabilitarcamposagendamento02();
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#584F4F !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    $buttonsToDisable = [
                        'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                        'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                        'btn_novopaciente', 'btn_usodemedicamentos', 'btn_clique',
                        'btn_finalizouatendimento', 'btn_duplicaragendamento'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    // Lógica corrigida para duplicar agendamento
                    $statusValue = $object->status ?? '';
                    $agendamentoNovo = $object->agendamento_novo ?? '';

                    if (($statusValue != '01' && $statusValue != '02') || empty($statusValue)) {
                        if (empty($agendamentoNovo) && $tipoAgendamento != 3) {
                            TButton::enableField(self::$formName, 'btn_duplicaragendamento');
                        }
                    }
                }    

                if ($status == '06') //Marcar retorno
                {
                    if (method_exists($this, 'desabilitarcamposagendamento02')) {
                        self::desabilitarcamposagendamento02();
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#046d8b !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    $buttonsToDisable = [
                        'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                        'btn_protocoloepreparo', 'btn_gerarfinanceiro', 'btn_novopaciente',
                        'btn_usodemedicamentos', 'btn_finalizouatendimento', 'btn_clique',
                        'btn_duplicaragendamento'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    // Lógica corrigida para duplicar agendamento
                    $statusValue = $object->status ?? '';
                    $agendamentoNovo = $object->agendamento_novo ?? '';

                    if (($statusValue != '01' && $statusValue != '02') || empty($statusValue)) {
                        if (empty($agendamentoNovo) && $tipoAgendamento != 3) {
                            TButton::enableField(self::$formName, 'btn_duplicaragendamento');
                        }
                    }
                }    

                if ($status == '12')  //Finalizou o atendimento
                {
                    if (method_exists($this, 'desabilitarcamposagendamento01'))
                    {
                        self::desabilitarcamposagendamento01();
                    }

                    $field1 = $this->form->getField('horario_inicial');
                    if ($field1) {
                        $field1->setEditable(false);
                    }

                    $field2 = $this->form->getField('horario_final');
                    if ($field2) {
                        $field2->setEditable(false);
                    }

                    if (method_exists('TNumeric', 'disableField')) {
                        TNumeric::disableField(self::$formName,  'valor_original');
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#414749 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                    $buttonsToDisable = [
                        'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                        'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                        'btn_novopaciente', 'btn_clique', 'btn_finalizouatendimento',
                        'btn_usodemedicamentos'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    TButton::enableField(self::$formName,  'btn_duplicaragendamento');
                }

                if ($status == '98') //Lista de espera
                {  
                    if (method_exists($this, 'desabilitarcamposagendamento01')) {
                        self::desabilitarcamposagendamento01();
                    }

                    $buttonsToDisable = [
                        'btn_clique', 'btn_atualizardadospaciente', 'btn_confirmar', 'btn_desmarcar',
                        'btn_remarcar', 'btn_naocompareceu', 'btn_marcarretorno', 'btn_protocoloepreparo',
                        'btn_gerarfinanceiro', 'btn_usodemedicamentos', 'btn_finalizouatendimento',
                        'btn_duplicaragendamento', 'btn_cancelaragendamento'
                    ];

                    foreach ($buttonsToDisable as $button) {
                        TButton::disableField(self::$formName, $button);
                    }

                    TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#FD9309 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');
                }

                if ($status == '99') //T:Horário indisponível
                {
                    if (method_exists('parent', 'closeWindow')) {
                        parent::closeWindow();
                    }
                    new TMessage('error','<b>' . TSession::getValue('username') . '</b>'  . '  -  ' . 'Para editar utilize a opção Bloquear horário.');
                }
            }

            // Fire events com verificação
            if (method_exists($this, 'fireEvents')) 
            {
                $this->fireEvents($object);
            }

            // Calcular tempo de carregamento
            $endTime = microtime(true);
            $loadTime = round(($endTime - $startTime) * 1000); // Tempo em milissegundos

            // Fechamento seguro da transação
            TTransaction::close();

            // Script para remover o loading com feedback de tempo
            $removeLoadingScript = "
                setTimeout(function() {
                    // Completar barra de progresso
                    if (window.loadingProgressInterval) {
                        clearInterval(window.loadingProgressInterval);
                    }

                    var progress = document.querySelector('#loading-overlay div div div:last-child div');
                    if (progress) {
                        progress.style.width = '100%';
                    }

                    // Mostrar tempo de carregamento
                    var message = document.querySelector('#loading-overlay div div div:nth-child(2)');
                    if (message) {
                        message.innerHTML = '<strong>Concluído!</strong><br>Dados carregados em {$loadTime}ms';
                    }

                    // Remover loading após pequena pausa
                    setTimeout(function() {
                        var overlay = document.getElementById('loading-overlay');
                        if (overlay) {
                            overlay.style.opacity = '0';
                            overlay.style.transition = 'opacity 0.3s ease';
                            setTimeout(function() {
                                overlay.remove();
                            }, 300);
                        }
                    }, 500);
                }, 100);
            ";

            TScript::create($removeLoadingScript);
        }
        else
        {
            $this->form->clear();
        }
    }
    catch (Exception $e)
    {
        // Remover loading em caso de erro
        $removeLoadingScript = "
            var overlay = document.getElementById('loading-overlay');
            if (overlay) {
                overlay.remove();
            }
        ";
        TScript::create($removeLoadingScript);

        new TMessage('error','<b>' . TSession::getValue('username') . '</b>'  . '  -  ' . $e->getMessage());

        // Rollback apenas se a transação estiver ativa
        try {
            if (TTransaction::getDatabase()) {
                TTransaction::rollback();
            }
        }
        catch (Exception $rollbackException)
        {
            error_log("Erro no rollback: " . $rollbackException->getMessage());
        }
    }
}
*/
*/
?>

*---------------------------------------------------------------------------
 public function onEdit( $param )//</ini>
    {
        try
        {
            if (empty($param['key']))
            {
                $this->form->clear();
                TScript::unmaskWidget('btn_save'); // exemplo de correção visual
                return;
            }

            if (isset($param['key']))
            {
                $key = $param['key']; 
                TTransaction::open(self::$database);

                $object = new Agendamento($key); // instantiates the Active Record //</blockLine>
                    TSession::setValue('editaagenda', true);

                    // Verificações de segurança antes de acessar propriedades relacionadas
                    if ($object->paciente && isset($this->paciente_foto)) {
                        $object->paciente_foto = $object->paciente->foto ?? '';
                        $this->paciente_foto->src = $object->paciente->caminho_foto ?? '';
                        $object->paciente_tipo_atendimento = $object->paciente->tipo_atendimento ?? '';
                    }

                    // Configurações com verificações de existência
                    if (isset($this->ContainerContaParcelasListReceber) && !empty($object->paciente_id)) {
                        $this->ContainerContaParcelasListReceber->setParameter('agenda_cliente_id', $object->paciente_id);
                    }

                    if (isset($this->horariosesalas01) && !empty($object->profissional_id)) {
                        $this->horariosesalas01->setParameter('profissional_id', $object->profissional_id);
                    }
/*
                //</beforeSetDataAutoCode> //</blockLine>
//<generatedAutoCode>
                $object->paciente_foto = $object->paciente->foto;

                $this->paciente_foto->src = $object->paciente->caminho_foto;            $object->paciente_tipo_atendimento = $object->paciente->tipo_atendimento;
                $this->ContainerContaParcelasListReceber->setParameter('agenda_cliente_id', $object->paciente_id);
                $this->horariosesalas01->setParameter('profissional_id', $object->profissional_id);
                $object->view = !empty($param['view']) ? $param['view'] : 'agendaWeek';
//</generatedAutoCode>

*/
                     $object->view = !empty($param['view']) ? $param['view'] : 'agendaWeek';
                 $this->form->setData($object); // fill the form //</blockLine>

                    // Configurações de campos com verificações
                    if (!empty($object->guia_id) && $this->form->getField('guia_id'))
                    {
                            $this->form->getField('guia_id')->setEditable(false);
                    }

                    if (!empty($object->guia_idsecundaria) && $this->form->getField('guia_idsecundaria'))
                    {
                            $this->form->getField('guia_idsecundaria')->setEditable(false);
                    }

                    // Verificação de status cancelado
                    if ($object->cancelado == 'S')
                    {
                            // Lista de botões para desabilitar
                            $buttonsToDisable = [
                                'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                                'btn_protocoloepreparo', 'btn_gerarfinanceiro', 'btn_novopaciente',
                                'btn_usodemedicamentos', 'btn_finalizouatendimento', 'btn_marcarretorno',
                                'btn_duplicaragendamento', 'btn_cancelaragendamento', 'btn_atualizardadospaciente',
                                'btn_clique'
                            ];

                            foreach ($buttonsToDisable as $button) 
                            {
                                TButton::disableField(self::$formName, $button);
                            }

                            // Lista de campos para desabilitar
                            $fieldsToDisable = 
                            [
                                'empresa_id', 'origemconsulta_id', 'cobranca_id', 'guia_id','guia_idsecundaria','ordematendimento','ambiente_id',
                                'paciente_id', 'fornecedor_id', 'produto_id', 'profissional_id',
                                'solicitante', 'entrega_resultado', 'status', 'horario_inicial',
                                'horario_final', 'ordematendimento', 'ambiente_id', 'valor_original'
                            ];

                            foreach ($fieldsToDisable as $fieldName)
                            {
                                $field = $this->form->getField($fieldName);
                                if ($field) {
                                    $field->setEditable(false);
                                }
                            }
                        }
                        else
                        {
                            $status = $object->status ?? '';

                            if ($status == 9999 || $status == '9999') //Lista de espera
                            {
                                $buttonsToDisable = [
                                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                                    'btn_protocoloepreparo', 'btn_gerarfinanceiro', 'btn_novopaciente',
                                    'btn_usodemedicamentos', 'btn_finalizouatendimento', 'btn_marcarretorno',
                                    'btn_duplicaragendamento', 'btn_cancelaragendamento', 'btn_atualizardadospaciente',
                                    'btn_clique'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                            }

                            if ($status == '01') //Aguardando confirmação
                            {
                                $fieldsToDisable =
                                [
                                    'empresa_id', 'origemconsulta_id', 'cobranca_id',
                                    'paciente_id', 'fornecedor_id', 'produto_id', 'profissional_id',
                                    'solicitante', 'entrega_resultado', 'status', 'horario_inicial',
                                    'horario_final'
                                ];

                                foreach ($fieldsToDisable as $fieldName)
                                {
                                    $field = $this->form->getField($fieldName);
                                    if ($field) {
                                        $field->setEditable(false);
                                    }
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#f0ad19 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                // Lista de botões para desabilitar
                                $buttonsToDisable = [
                                    'btn_remarcar', 'btn_naocompareceu', 'btn_novopaciente', 'btn_finalizouatendimento', 'btn_marcarretorno',
                                    'btn_duplicaragendamento', 'btn_clique'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                                TButton::enableField(self::$formName,  'btn_confirmar');
                                TButton::enableField(self::$formName,  'btn_desmarcar');
                                TButton::enableField(self::$formName,  'btn_protocoloepreparo');
                                TButton::enableField(self::$formName,  'btn_cancelaragendamento');

                                $gerouFinanceiro = $object->geroufinanceiro ?? 'N';
                                if ($gerouFinanceiro == 'N')
                                {
                                    TButton::enableField(self::$formName, 'btn_gerarfinanceiro');
                                }
                                else
                                {
                                    TButton::disableField(self::$formName, 'btn_gerarfinanceiro');                    
                                }
                            }

                            if ($status == '02')  //Confirmado
                            {
                                if (method_exists($this, 'desabilitarcamposagendamento01')) {
                                    //self::desabilitarcamposagendamento01();
                                    $this->desabilitarcamposagendamento01();
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#3C8DBC !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                // Lista de botões para desabilitar
                                $buttonsToDisable = [
                                    'btn_remarcar', 'btn_novopaciente', 'btn_clique', 'btn_duplicaragendamento', 'btn_marcarretorno'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                                TButton::enableField(self::$formName,  'btn_desmarcar');        
                                TButton::enableField(self::$formName,  'btn_remarcar');
                                TButton::enableField(self::$formName,  'btn_naocompareceu');
                                TButton::enableField(self::$formName,  'btn_protocoloepreparo');
                                TButton::enableField(self::$formName,  'btn_finalizouatendimento');

                                // Verificar se a classe existe E se tem o método
                                if (class_exists('TDateTime') && method_exists('TDateTime', 'disableField'))
                                {
                                    TDateTime::disableField(self::$formName, 'horario_inicial');
                                    TDateTime::disableField(self::$formName, 'horario_final');
                                }
                                else
                                {
                                    // Fallback: desabilitar usando o formulário diretamente
                                    $field1 = $this->form->getField('horario_inicial');
                                    if ($field1 && method_exists($field1, 'setEditable')) {
                                        $field1->setEditable(false);
                                    }

                                    $field2 = $this->form->getField('horario_final');
                                    if ($field2 && method_exists($field2, 'setEditable')) {
                                        $field2->setEditable(false);
                                    }
                                }

                                $gerouFinanceiro = $object->geroufinanceiro ?? 'N';
                                if ($gerouFinanceiro == 'N')
                                {
                                    TButton::enableField(self::$formName, 'btn_gerarfinanceiro');
                                }
                                else
                                {
                                    TButton::disableField(self::$formName, 'btn_gerarfinanceiro');                    
                                }
                            }

                            if ($status == '03')  //C:Desmarcar
                            {
                                if (method_exists($this, 'desabilitarcamposagendamento01'))
                                {
                                    //self::desabilitarcamposagendamento01();
                                    $this->desabilitarcamposagendamento01();
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#2A2D91 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                $buttonsToDisable = [
                                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu', 
                                    'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                                    'btn_novopaciente', 'btn_usodemedicamentos', 'btn_clique',
                                    'btn_finalizouatendimento'
                                ];

                                TButton::enableField(self::$formName,  'btn_duplicaragendamento');

                                // Verificar se a classe existe E se tem o método
                                if (class_exists('TDateTime') && method_exists('TDateTime', 'disableField')) {
                                    TDateTime::disableField(self::$formName, 'horario_inicial');
                                    TDateTime::disableField(self::$formName, 'horario_final');
                                } else {
                                    // Fallback: desabilitar usando o formulário diretamente
                                    $field1 = $this->form->getField('horario_inicial');
                                    if ($field1 && method_exists($field1, 'setEditable')) {
                                        $field1->setEditable(false);
                                    }

                                    $field2 = $this->form->getField('horario_final');
                                    if ($field2 && method_exists($field2, 'setEditable')) {
                                        $field2->setEditable(false);
                                    }
                                }                            
                            }    

                            if ($status == '04') //U:Não compareceu
                            {
                                if (method_exists($this, 'desabilitarcamposagendamento01'))
                                {
                                    //self::desabilitarcamposagendamento01();
                                    $this->desabilitarcamposagendamento01();
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#2BBBAD !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                $buttonsToDisable = [
                                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                                    'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                                    'btn_novopaciente', 'btn_usodemedicamentos', 'btn_clique',
                                    'btn_finalizouatendimento'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                                TButton::enableField(self::$formName,  'btn_duplicaragendamento');

                                // Verificar se a classe existe E se tem o método
                                if (class_exists('TDateTime') && method_exists('TDateTime', 'disableField'))
                                {
                                    TDateTime::disableField(self::$formName, 'horario_inicial');
                                    TDateTime::disableField(self::$formName, 'horario_final');
                                }
                                else
                                {
                                    // Fallback: desabilitar usando o formulário diretamente
                                    $field1 = $this->form->getField('horario_inicial');
                                    if ($field1 && method_exists($field1, 'setEditable')) {
                                        $field1->setEditable(false);
                                    }

                                    $field2 = $this->form->getField('horario_final');
                                    if ($field2 && method_exists($field2, 'setEditable')) {
                                        $field2->setEditable(false);
                                    }
                                }                            
                            }    

                            if ($status == '05') //E:Remarcar
                            {
                                if (method_exists($this, 'desabilitarcamposagendamento02')) {
                                    self::desabilitarcamposagendamento02();
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#584F4F !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                $buttonsToDisable = [
                                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                                    'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                                    'btn_novopaciente', 'btn_usodemedicamentos', 'btn_clique',
                                    'btn_finalizouatendimento', 'btn_duplicaragendamento'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                                // Lógica corrigida para duplicar agendamento
                                $statusValue = $object->status ?? '';
                                $agendamentoNovo = $object->agendamento_novo ?? '';

                                if (($statusValue != '01' && $statusValue != '02') || empty($statusValue)) {
                                    if (empty($agendamentoNovo)) {
                                        TButton::enableField(self::$formName, 'btn_duplicaragendamento');
                                    }
                                }
                            }    

                            if ($status == '06') //Marcar retorno
                            {
                                if (method_exists($this, 'desabilitarcamposagendamento02')) {
                                    self::desabilitarcamposagendamento02();
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#046d8b !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                $buttonsToDisable = [
                                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                                    'btn_protocoloepreparo', 'btn_gerarfinanceiro', 'btn_novopaciente',
                                    'btn_usodemedicamentos', 'btn_finalizouatendimento', 'btn_clique',
                                    'btn_duplicaragendamento'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                                // Lógica corrigida para duplicar agendamento
                                $statusValue = $object->status ?? '';
                                $agendamentoNovo = $object->agendamento_novo ?? '';

                                if (($statusValue != '01' && $statusValue != '02') || empty($statusValue)) {
                                    if (empty($agendamentoNovo)) {
                                        TButton::enableField(self::$formName, 'btn_duplicaragendamento');
                                    }
                                }
                            }    

                            if ($status == '12')  //Finalizou o atendimento
                            {
                                if (method_exists($this, 'desabilitarcamposagendamento01'))
                                {
                                    //self::desabilitarcamposagendamento01();
                                    $this->desabilitarcamposagendamento01();
                                }

                                $field1 = $this->form->getField('horario_inicial');
                                if ($field1) {
                                    $field1->setEditable(false);
                                }

                                $field2 = $this->form->getField('horario_final');
                                if ($field2) {
                                    $field2->setEditable(false);
                                }

                                if (method_exists('TNumeric', 'disableField')) {
                                    TNumeric::disableField(self::$formName,  'valor_original');
                                }

                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#414749 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');

                                $buttonsToDisable = [
                                    'btn_confirmar', 'btn_desmarcar', 'btn_remarcar', 'btn_naocompareceu',
                                    'btn_marcarretorno', 'btn_protocoloepreparo', 'btn_gerarfinanceiro',
                                    'btn_novopaciente', 'btn_clique', 'btn_finalizouatendimento',
                                    'btn_usodemedicamentos'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }

                                TButton::enableField(self::$formName,  'btn_duplicaragendamento');
                            }

                            if ($status == '98') //Lista de espera
                            {  
                                if (method_exists($this, 'desabilitarcamposagendamento01')) {
                                    //self::desabilitarcamposagendamento01();
                                    $this->desabilitarcamposagendamento01();
                                }

                                $buttonsToDisable = [
                                    'btn_clique', 'btn_atualizardadospaciente', 'btn_confirmar', 'btn_desmarcar',
                                    'btn_remarcar', 'btn_naocompareceu', 'btn_marcarretorno', 'btn_protocoloepreparo',
                                    'btn_gerarfinanceiro', 'btn_usodemedicamentos', 'btn_finalizouatendimento',
                                    'btn_duplicaragendamento', 'btn_cancelaragendamento'
                                ];

                                foreach ($buttonsToDisable as $button) 
                                {
                                    TButton::disableField(self::$formName, $button);
                                }
                                TScript::create('$("[name=statuscor]").attr("style", "color:white; font-weight:bold !important; background-color:#FD9309 !important; width: 200px; height: 31px; display: inline-block; border-radius: 6%; border: 1px solid;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2)")');
                            }

                            if ($status == '99') //T:Horário indisponível
                            {
                                if (method_exists('parent', 'closeWindow')) {
                                    parent::closeWindow();
                                }
                                new TMessage('error','<b>' . TSession::getValue('username') . '</b>'  . '  -  ' . 'Para editar utilize a opção Bloquear horário.');
                            }
                        }

/* REMOVIDA A SEGUNDA CHAMADA DUPLICADA DO setData                   
                 $this->form->setData($object); // fill the form //</blockLine>

                //</afterSetDataAutoCode> //</blockLine>
//<generatedAutoCode>

                $this->fireEvents($object);

//</generatedAutoCode>

*/
                // Fire events com verificação
                if (method_exists($this, 'fireEvents')) 
                {
                    $this->fireEvents($object);
                }
                // Fechamento seguro da transação
                TTransaction::close(); // close the transaction 
        }
        else
        {
            $this->form->clear();
            TScript::create("
                $('#loading-message').hide();
                $('[name=btn_save]').prop('disabled', false);
            ");
        }

        }
        catch (Exception $e) // in case of exception
        {
            new TMessage('error','<b>' . TSession::getValue('username') . '</b>'  . '  -  ' . $e->getMessage());
            TScript::create("
                $('#loading-message').hide();
                $('[name=btn_save]').prop('disabled', false);
            ");
            TTransaction::rollback();
        }
        finally
        {
            // Garantia de fechamento da transação
            if (TTransaction::getDatabase())
            {
                TTransaction::close();
            }
        }
    }


    /*******************************************************
    /* Editor editável -> fundo vermelho */
.custom-html-editor.adianti-html-editor[contenteditable="true"],
.custom-html-editor.adianti-html-editor.editable {
    background-color: #ffcccc !important; /* vermelho claro */
}

/* Editor bloqueado -> fundo verde */
.custom-html-editor.adianti-html-editor[contenteditable="false"],
.custom-html-editor.adianti-html-editor.readonly {
    background-color: #ccffcc !important; /* verde claro */
}

/* Aplica também ao iframe do CKEditor/TinyMCE se possível */
/* Para CKEditor, o estilo precisa ser injetado no corpo do iframe */

/* CSS para o editor de texto */
.editable {
    background-color: red; /* Fundo vermelho quando editável */
}

.read-only {
    background-color: green; /* Fundo verde quando bloqueado */
}


/**
 * CSS Personalizado para THtmlEditor/note-editor do Adianti Framework
 * 
 * Este arquivo define cores de fundo específicas para diferentes estados do editor:
 * - Vermelho quando editável
 * - Verde quando bloqueado para edição
 * 
 * Versão: 1.0
 * Compatível com: Adianti Framework 7.x/8.x
 */

/* ========================================
   ESTILOS PARA THTMLEDITOR EDITÁVEL
   ======================================== */

/* Editor principal quando editável */
.thtmleditor:not([readonly]):not([disabled]) {
    background-color: #ffebee !important; /* Fundo vermelho claro */
}

/* Área de edição do Summernote quando editável */
.thtmleditor:not([readonly]):not([disabled]) .note-editor .note-editing-area .note-editable {
    background-color: #ffcdd2 !important; /* Fundo vermelho mais visível */
    border: 2px solid #f44336 !important; /* Borda vermelha */
}

/* Container do editor quando editável */
.thtmleditor:not([readonly]):not([disabled]) .note-editor {
    background-color: #ffebee !important;
    border: 1px solid #f44336 !important;
}

/* Toolbar quando editável */
.thtmleditor:not([readonly]):not([disabled]) .note-toolbar {
    background-color: #ffcdd2 !important;
    border-bottom: 1px solid #f44336 !important;
}

/* ========================================
   ESTILOS PARA THTMLEDITOR BLOQUEADO
   ======================================== */

/* Editor principal quando bloqueado (readonly ou disabled) */
.thtmleditor[readonly],
.thtmleditor[disabled] {
    background-color: #e8f5e8 !important; /* Fundo verde claro */
}

/* Área de edição do Summernote quando bloqueado */
.thtmleditor[readonly] .note-editor .note-editing-area .note-editable,
.thtmleditor[disabled] .note-editor .note-editing-area .note-editable {
    background-color: #c8e6c9 !important; /* Fundo verde mais visível */
    border: 2px solid #4caf50 !important; /* Borda verde */
    cursor: not-allowed !important;
}

/* Container do editor quando bloqueado */
.thtmleditor[readonly] .note-editor,
.thtmleditor[disabled] .note-editor {
    background-color: #e8f5e8 !important;
    border: 1px solid #4caf50 !important;
}

/* Toolbar quando bloqueado (escondida ou desabilitada) */
.thtmleditor[readonly] .note-toolbar,
.thtmleditor[disabled] .note-toolbar {
    background-color: #c8e6c9 !important;
    border-bottom: 1px solid #4caf50 !important;
    opacity: 0.6 !important;
}

/* Desabilitar botões da toolbar quando bloqueado */
.thtmleditor[readonly] .note-toolbar .btn,
.thtmleditor[disabled] .note-toolbar .btn {
    pointer-events: none !important;
    opacity: 0.5 !important;
}

/* ========================================
   ESTILOS ALTERNATIVOS BASEADOS EM CLASSES
   ======================================== */

/* Caso o Adianti use classes específicas ao invés de atributos */

/* Editor editável via classe */
.thtmleditor.editable,
.thtmleditor.enabled {
    background-color: #ffebee !important;
}

.thtmleditor.editable .note-editor .note-editing-area .note-editable,
.thtmleditor.enabled .note-editor .note-editing-area .note-editable {
    background-color: #ffcdd2 !important;
    border: 2px solid #f44336 !important;
}

/* Editor bloqueado via classe */
.thtmleditor.readonly,
.thtmleditor.disabled {
    background-color: #e8f5e8 !important;
}

.thtmleditor.readonly .note-editor .note-editing-area .note-editable,
.thtmleditor.disabled .note-editor .note-editing-area .note-editable {
    background-color: #c8e6c9 !important;
    border: 2px solid #4caf50 !important;
    cursor: not-allowed !important;
}

/* ========================================
   ESTILOS PARA NOTE-EDITOR (ALTERNATIVO)
   ======================================== */

/* Caso seja usado note-editor ao invés de thtmleditor */

/* Note-editor editável */
.note-editor:not(.readonly):not(.disabled) {
    background-color: #ffebee !important;
}

.note-editor:not(.readonly):not(.disabled) .note-editing-area .note-editable {
    background-color: #ffcdd2 !important;
    border: 2px solid #f44336 !important;
}

/* Note-editor bloqueado */
.note-editor.readonly,
.note-editor.disabled {
    background-color: #e8f5e8 !important;
}

.note-editor.readonly .note-editing-area .note-editable,
.note-editor.disabled .note-editing-area .note-editable {
    background-color: #c8e6c9 !important;
    border: 2px solid #4caf50 !important;
    cursor: not-allowed !important;
}

/* ========================================
   ESTILOS RESPONSIVOS E ACESSIBILIDADE
   ======================================== */

/* Garantir que as cores sejam visíveis em diferentes temas */
@media (prefers-color-scheme: dark) {
    /* Ajustes para tema escuro */
    .thtmleditor:not([readonly]):not([disabled]) {
        background-color: #4a1a1a !important; /* Vermelho escuro */
    }
    
    .thtmleditor[readonly],
    .thtmleditor[disabled] {
        background-color: #1a4a1a !important; /* Verde escuro */
    }
}

/* Estilos para impressão */
@media print {
    .thtmleditor:not([readonly]):not([disabled]) {
        background-color: #ffebee !important;
        -webkit-print-color-adjust: exact !important;
        color-adjust: exact !important;
    }
    
    .thtmleditor[readonly],
    .thtmleditor[disabled] {
        background-color: #e8f5e8 !important;
        -webkit-print-color-adjust: exact !important;
        color-adjust: exact !important;
    }
}

/* ========================================
   ANIMAÇÕES E TRANSIÇÕES
   ======================================== */

/* Transição suave entre estados */
.thtmleditor,
.thtmleditor .note-editor,
.thtmleditor .note-editing-area .note-editable {
    transition: background-color 0.3s ease, border-color 0.3s ease !important;
}

/* ========================================
   COMENTÁRIOS DE USO
   ======================================== */

/*
INSTRUÇÕES DE USO:

1. Inclua este arquivo CSS após os arquivos CSS do Adianti Framework
2. Certifique-se de que o CSS seja carregado em todas as páginas que usam THtmlEditor
3. O CSS usa !important para garantir precedência sobre estilos padrão

EXEMPLO DE INCLUSÃO NO HTML:
<link rel="stylesheet" href="path/to/thtmleditor-custom.css">

EXEMPLO DE INCLUSÃO NO PHP (Adianti):
TPage::include_css('path/to/thtmleditor-custom.css');

PERSONALIZAÇÃO:
- Para alterar as cores, modifique os valores de background-color
- Para ajustar bordas, modifique os valores de border
- Para diferentes tons, use ferramentas como https://material.io/design/color/

CORES USADAS:
- Vermelho editável: #ffebee (claro), #ffcdd2 (médio), #f44336 (escuro)
- Verde bloqueado: #e8f5e8 (claro), #c8e6c9 (médio), #4caf50 (escuro)
*/

   public function onCancel($param = null) 
    {
        try
        {
            // PRIMEIRA VERIFICAÇÃO: Checar se já foi cancelado ANTES de qualquer coisa
            if (isset($param["key"]) && !empty($param["key"]))
            {
                try 
                {
                    TTransaction::open(self::$database);
                    
                    $key = $param["key"];
                    $verificar_cancelamento = TTransaction::get()->query(
                        "SELECT c.cancelado, c.tipo_cancelamento, c.usuario_cancelamento, 
                                c.data_cancelamento, c.hora_cancelamento, c.motivo_cancelamento,
                                u.name as nome_usuario_cancelamento
                        FROM conta c 
                        LEFT JOIN system_users u ON c.usuario_cancelamento = u.id 
                        WHERE c.id = " . intval($key)
                    )->fetch();
                    
                    // Se já foi cancelado, mostrar informações e parar o processo
                    if ($verificar_cancelamento && $verificar_cancelamento['cancelado'] == 'S')
                    {
                        $tipo_descricao = ($verificar_cancelamento['tipo_cancelamento'] == 'C') ? 'Via contas a receber' : 'Via agendamento';
                        
                        $nome_usuario = $verificar_cancelamento['nome_usuario_cancelamento'] ?: 
                                    'ID: ' . $verificar_cancelamento['usuario_cancelamento'];
                        
                        $mensagem_cancelado = "
                            este registro já foi cancelado!<br><br>
                            <strong>Tipo do cancelamento:</strong> {$tipo_descricao}<br>
                            <strong>Usuário:</strong> {$nome_usuario}<br>
                            <strong>Data:</strong> " . date('d/m/Y', strtotime($verificar_cancelamento['data_cancelamento'])) . "<br>
                            <strong>Hora:</strong> {$verificar_cancelamento['hora_cancelamento']}<br>
                            <strong>Motivo:</strong> {$verificar_cancelamento['motivo_cancelamento']}
                        ";
                        new TMessage('info',  '<b>' .TSession::getValue('username').  '</b>' . $mensagem_cancelado);
                        TTransaction::close();
                        return; // Para o processo aqui
                    }
                    
                    TTransaction::close();
                }
                catch (Exception $e)
                {
                    TTransaction::rollback();
                    new TMessage('error', 'Erro ao verificar status do cancelamento: ' . $e->getMessage());
                    return;
                }
            }

            // SE NÃO FOI CANCELADO, CONTINUA O PROCESSO NORMAL
            if(isset($param["confirm"]) && $param["confirm"] == 1)
            {
                // Capturar motivo - PRIORIDADE para GET (vem da URL)
                $motivo = '';
                if (isset($param['motivo_cancelamento']) && !empty(trim($param['motivo_cancelamento']))) 
                {
                    $motivo = trim($param['motivo_cancelamento']);
                }
                elseif (isset($_GET['motivo_cancelamento']) && !empty(trim($_GET['motivo_cancelamento']))) 
                {
                    $motivo = trim($_GET['motivo_cancelamento']);
                }
                elseif (isset($_POST['motivo_cancelamento']) && !empty(trim($_POST['motivo_cancelamento']))) 
                {
                    $motivo = trim($_POST['motivo_cancelamento']);
                }
                elseif (isset($_REQUEST['motivo_cancelamento']) && !empty(trim($_REQUEST['motivo_cancelamento']))) 
                {
                    $motivo = trim($_REQUEST['motivo_cancelamento']);
                }

                // Validação
                if (empty($motivo)) 
                {
                    new TMessage('error', 'O motivo do cancelamento é obrigatório!');
                    $this->onRequestCancelReason($param);
                    return;
                }

                if (strlen($motivo) < 3) 
                {
                    new TMessage('error', 'O motivo deve ter pelo menos 3 caracteres.');
                    $this->onRequestCancelReason($param);
                    return;
                }

                try
                {
                    $key = $param["key"];
    /*
                    // DEBUG COMPLETO - VER O QUE ESTÁ CHEGANDO
                    echo "<br><strong>DEBUG CANCELAMENTO:</strong><br>";
                    echo "KEY recebida: " . var_export($key, true) . "<br>";
                    echo "Motivo recebido: " . var_export($motivo, true) . "<br>";
                    echo "Parâmetros completos: " . var_export($param, true) . "<br>";
                    echo "GET: " . var_export($_GET, true) . "<br>";
                    echo "POST: " . var_export($_POST, true) . "<br>";
                    echo "<hr>";
    */
                    if (empty($key)) 
                    {
                        new TMessage('error', 'ID do registro não foi recebido!');
                        return;
                    }

                    TTransaction::open(self::$database);

                    // Verificar se o registro existe ANTES de tentar atualizar
                    $verificar_existe = TTransaction::get()->query("SELECT id, cancelado FROM conta WHERE id = " . intval($key))->fetch();

                    //echo "<strong>Registro encontrado:</strong> " . var_export($verificar_existe, true) . "<br>";

                    if (!$verificar_existe) 
                    {
                        new TMessage('error', 'Registro ID ' . $key . ' não foi encontrado na tabela conta!');
                        TTransaction::close();
                        return;
                    }

                    // VERIFICAÇÃO DUPLA: Checar novamente se já foi cancelado (caso alguém tenha cancelado entre as verificações)
                    if ($verificar_existe['cancelado'] == 'S')
                    {
                        new TMessage('info', 'Este registro já foi cancelado por outro usuário durante o processo.');
                        TTransaction::close();
                        return;
                    }

                    // EXECUTAR UPDATE
                    $userid = TSession::getValue('userid');
                    $data_atual = date('Y-m-d');
                    $hora_atual = date('H:i:s');

                    /*
                    'C' = 'Via contas';
                    'A' = 'Via agendamento';
                    */
                    $sql = "UPDATE conta SET 
                                cancelado = 'S',
                                tipo_cancelamento = 'C',
                                usuario_cancelamento = ?,
                                data_cancelamento = ?,
                                hora_cancelamento = ?,
                                motivo_cancelamento = ?
                            WHERE id = ?";

                    //echo "<strong>SQL a ser executado:</strong><br>" . $sql . "<br>";
                    //echo "<strong>Parâmetros:</strong> UserID=" . $userid . ", Data=" . $data_atual . ", Hora=" . $hora_atual . ", Motivo=" . $motivo . ", ID=" . $key . "<br>";

                    $conn = TTransaction::get();
                    $stmt = $conn->prepare($sql);

                    $result = $stmt->execute([
                        $userid,
                        $data_atual, 
                        $hora_atual,
                        $motivo,
                        intval($key)
                    ]);

                    //echo "<strong>Resultado do UPDATE:</strong> " . var_export($result, true) . "<br>";
                    //echo "<strong>Linhas afetadas:</strong> " . $stmt->rowCount() . "<br>";

                    // Verificar se foi atualizado
                    $verificar_depois = $conn->query("SELECT cancelado, tipo_cancelamento, motivo_cancelamento FROM conta WHERE id = " . intval($key))->fetch();
                    //echo "<strong>Dados após UPDATE:</strong> " . var_export($verificar_depois, true) . "<br>";

                    TTransaction::close();

                    if ($stmt->rowCount() > 0)
                    {
                        new TMessage('info', '<b>' .TSession::getValue('username').  '</b>' . 'Registro ' . $key . ' cancelado com sucesso! Motivo: ' . $motivo);
                    } 
                    else 
                    {
                        new TMessage('error', '<b>' .TSession::getValue('username').  '</b>' .'Nenhuma linha foi atualizada. Verifique o ID: ' . $key);
                    }

                    // NÃO fechar modal ainda para ver o debug
                    //$this->onReload($param);
                }
                catch (Exception $e)
                {
                    new TMessage('error', 'Erro: ' . $e->getMessage());
                    TTransaction::rollback();
                }
            }
            elseif(isset($param["show_form"]) && $param["show_form"] == 1)
            {
                $this->onRequestCancelReason($param);
            }
            else
            {
                $usuario_nome = TSession::getValue('username') ?: TSession::getValue('login');

                $action_show_form = new TAction(array($this, 'onCancel'));
                $action_show_form->setParameters($param);
                $action_show_form->setParameter('show_form', 1);

                $mensagem_confirmacao = "<b>{$usuario_nome}</b><br><br>Deseja realmente cancelar este registro?";
                new TQuestion($mensagem_confirmacao, $action_show_form);
            }

            //</autoCode>
        }
        catch (Exception $e) 
        {
            new TMessage('error', $e->getMessage());
            TTransaction::rollback();            
        }
    }

public function onRequestCancelReason($param = null)
{
    try
    {
        $form = new BootstrapFormBuilder('form_cancel_reason');
        $form->setFormTitle('Motivo do Cancelamento');

        $motivo = new TText('motivo_cancelamento');
        $motivo->setSize('100%');
        $motivo->placeholder = 'Digite o motivo do cancelamento';

        $form->addFields([new TLabel('Motivo: <span style="color:red;">*</span>')], [$motivo]);

        // Criar action vazia para o botão confirmar
        $action_confirm = new TAction(array($this, 'onCancel'));
        $action_confirm->setParameters($param);
        $action_confirm->setParameter('confirm', 1);

        $form->addAction('Confirmar', $action_confirm, 'fa:check');
        $form->addAction('Cancelar', new TAction(array($this, 'onReload')), 'fa:times');

        // JavaScript para interceptar o botão ANTES da action
        TScript::create("
            setTimeout(function() {
                $('button:contains(\"Confirmar\")').off('click').on('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();

                    var motivo = $('input[name=\"motivo_cancelamento\"]').val().trim();

                    if (!motivo) {
                        alert('Digite o motivo do cancelamento!');
                        $('input[name=\"motivo_cancelamento\"]').focus();
                        return false;
                    }

                    if (motivo.length < 3) {
                        alert('Motivo deve ter pelo menos 3 caracteres!');
                        $('input[name=\"motivo_cancelamento\"]').focus();
                        return false;
                    }

                    // ENVIO DIRETO
                    window.location.href = 'engine.php?class=" . get_class($this) . "&method=onCancel&confirm=1&key=" . $param['key'] . "&motivo_cancelamento=' + encodeURIComponent(motivo);
                });

                // Foco no campo
                $('input[name=\"motivo_cancelamento\"]').focus();
            }, 100);
        ");

        $window = TWindow::create('Motivo do Cancelamento', 0.6, 0.3);
        $window->add($form);
        $window->show();
    }
    catch (Exception $e)
    {
        new TMessage('error', $e->getMessage());
    }
}



/*
    public function calcular($param)
    {
        try {
            TTransaction::open('seu_banco_de_dados');

            $data_inicial = $param['data_inicial'];
            $data_final = $param['data_final'];
            $registros_ids = $param['registros'];

            foreach ($registros_ids as $id) {
                $registro = new SuaClasse($id); // Substitua SuaClasse
                $valor_final = $registro->valor_venda + $registro->juros - $registro->desconto;
                $registro->valor_repasse = $valor_final * ($registro->percentual_repasse / 100);
                $registro->fechamento = 'S';
                $registro->store();
            }

            TTransaction::close();
            new TMessage('info', 'Cálculo em lote realizado com sucesso!');
        } catch (Exception $e) {
            TTransaction::rollback();
            new TMessage('error', 'Erro ao realizar cálculo em lote: ' . $e->getMessage());
        }
    }

    public function onCalculate($param)
    {
        try {
            TTransaction::open('database'); // Substitua pelo nome do seu banco de dados
            $repository = new TRepository('Venda'); // Substitua pelo nome da sua tabela/entidade

            $criteria = new TCriteria;
            if (!empty($param['periodo'])) {
                $criteria->add(new TFilter('data_venda', '=', $param['periodo']));
            }

            $registros = $repository->load($criteria);
            if ($registros) {
                foreach ($registros as $registro) {
                    // Realiza o cálculo
                    $valor_final = ($registro->valor_venda + $registro->juros) - $registro->desconto;
                    $registro->valor_repasse = $valor_final * ($registro->percentual_repasse / 100);
                    $registro->fechamento = 'S';

                    $registro->store(); // Salva no banco de dados
                }
                new TMessage('info', 'Cálculo realizado com sucesso!');
            } else {
                new TMessage('error', 'Nenhum registro encontrado.');
            }
            TTransaction::close();
        } catch (Exception $e) {
            new TMessage('error', $e->getMessage());
            TTransaction::rollback();
        }
    }

*/