<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><category>pcsoft.br.windev</category><copyright>Copyright 2026, PC SOFT</copyright><lastBuildDate>16 Aug 2025 03:31:49 Z</lastBuildDate><pubDate>14 Aug 2025 07:35:42 Z</pubDate><description>Bom dia! &#13;
&#13;
Vou criar um sistema completo de CRUD REST API em WLanguage para você. Criarei uma classe servidor que gera automaticamente os endpoints REST e uma classe cliente para consumir a API.​​​​​​​​​​​​​​​​&#13;
&#13;
[code:wl]&#13;
&#13;
[/code]&#13;
&#13;
&#13;
[code:wl]&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCRUDServer&#13;
// Descrição: Classe para gerar automaticamente CRUD REST API de qualquer tabela&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
CRestCRUDServer is Class&#13;
// Atributos privados&#13;
PRIVATE&#13;
m_sTableName is string&#13;
m_sConnectionString is string&#13;
m_stFieldsInfo is array of stFieldInfo&#13;
m_sPrimaryKey is string&#13;
m_stForeignKeys is array of stForeignKey&#13;
m_stMandatoryFields is array of string&#13;
m_nPort is int = 8080&#13;
m_sBasePath is string = “/api/v1”&#13;
END&#13;
&#13;
// Estruturas auxiliares&#13;
stFieldInfo is Structure&#13;
sName is string&#13;
nType is int&#13;
nSize is int&#13;
bMandatory is boolean&#13;
bPrimaryKey is boolean&#13;
sDefaultValue is string&#13;
END&#13;
&#13;
stForeignKey is Structure&#13;
sFieldName is string&#13;
sReferencedTable is string&#13;
sReferencedField is string&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PÚBLICOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa a classe com a tabela/view/query&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sTableOrQuery is string, sConnection is string = “”)&#13;
m_sTableName = sTableOrQuery&#13;
m_sConnectionString = sConnection&#13;
&#13;
```&#13;
// Analisa a estrutura da tabela/query&#13;
IF NOT AnalyzeTableStructure() THEN&#13;
	ExceptionThrow(1, "Erro ao analisar estrutura da tabela: " + m_sTableName)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// StartServer: Inicia o servidor REST&#13;
//——————————————————————————&#13;
PROCEDURE StartServer(nPort is int = 8080, sBasePath is string = “/api/v1”) : boolean&#13;
m_nPort = nPort&#13;
m_sBasePath = sBasePath&#13;
&#13;
```&#13;
// Configura as rotas REST automaticamente&#13;
ConfigureRoutes()&#13;
&#13;
// Inicia o servidor web&#13;
IF NOT WebServerStart(m_nPort) THEN&#13;
	RESULT False&#13;
END&#13;
&#13;
Info("Servidor REST iniciado na porta " + m_nPort + StringBuild(CR + "Endpoints disponíveis:" + CR + ...&#13;
	"GET %1/%2 - Listar todos" + CR + ...&#13;
	"GET %1/%2/{id} - Obter por ID" + CR + ...&#13;
	"POST %1/%2 - Criar novo" + CR + ...&#13;
	"PUT %1/%2/{id} - Atualizar" + CR + ...&#13;
	"DELETE %1/%2/{id} - Excluir", m_sBasePath, m_sTableName))&#13;
&#13;
RESULT True&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PRIVADOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeTableStructure: Analisa dinamicamente a estrutura da tabela&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeTableStructure() : boolean&#13;
stField is stFieldInfo&#13;
sAnalysis is string&#13;
nFieldCount is int&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
	// Obtém informações da análise&#13;
	sAnalysis = HListFile()&#13;
	&#13;
	// Verifica se a tabela existe na análise&#13;
	IF Position(sAnalysis, m_sTableName, firstRank, IgnoreCase) = 0 THEN&#13;
		// Tenta como query SQL&#13;
		IF NOT AnalyzeSQLQuery() THEN&#13;
			RESULT False&#13;
		END&#13;
	END&#13;
	&#13;
	// Obtém número de campos&#13;
	nFieldCount = HNbItem(m_sTableName)&#13;
	&#13;
	// Analisa cada campo&#13;
	FOR i = 1 TO nFieldCount&#13;
		stField.sName = HListItem(m_sTableName, i)&#13;
		stField.nType = HItemType(m_sTableName, stField.sName)&#13;
		stField.nSize = HItemSize(m_sTableName, stField.sName)&#13;
		stField.bMandatory = HItemIsMandatory(m_sTableName, stField.sName)&#13;
		stField.bPrimaryKey = HItemIsKey(m_sTableName, stField.sName)&#13;
		&#13;
		// Adiciona ao array de campos&#13;
		ArrayAdd(m_stFieldsInfo, stField)&#13;
		&#13;
		// Identifica chave primária&#13;
		IF stField.bPrimaryKey THEN&#13;
			m_sPrimaryKey = stField.sName&#13;
		END&#13;
		&#13;
		// Adiciona campos obrigatórios&#13;
		IF stField.bMandatory THEN&#13;
			ArrayAdd(m_stMandatoryFields, stField.sName)&#13;
		END&#13;
	END&#13;
	&#13;
	// Analisa chaves estrangeiras&#13;
	AnalyzeForeignKeys()&#13;
	&#13;
	RESULT True&#13;
	&#13;
EXCEPTION&#13;
	Error("Erro ao analisar estrutura: " + ExceptionInfo())&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeSQLQuery: Analisa query SQL personalizada&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeSQLQuery() : boolean&#13;
qryTemp is Data Source&#13;
&#13;
```&#13;
TRY&#13;
	// Executa a query para obter estrutura&#13;
	IF HExecuteSQLQuery(qryTemp, m_sConnectionString, m_sTableName) THEN&#13;
		// Usa HDescribeFile para obter estrutura da query&#13;
		HDescribeFile(qryTemp)&#13;
		m_sTableName = qryTemp..Name&#13;
		RESULT True&#13;
	END&#13;
	&#13;
	RESULT False&#13;
	&#13;
EXCEPTION&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeForeignKeys: Identifica chaves estrangeiras&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeForeignKeys()&#13;
stFK is stForeignKey&#13;
i is int&#13;
sLinkInfo is string&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
	// Verifica se o campo é uma chave estrangeira&#13;
	sLinkInfo = HListLink(m_sTableName, m_stFieldsInfo[i].sName)&#13;
	&#13;
	IF sLinkInfo &lt;&gt; "" THEN&#13;
		stFK.sFieldName = m_stFieldsInfo[i].sName&#13;
		// Parse das informações de link (simplificado)&#13;
		stFK.sReferencedTable = ExtractString(sLinkInfo, 1, TAB)&#13;
		stFK.sReferencedField = ExtractString(sLinkInfo, 2, TAB)&#13;
		&#13;
		ArrayAdd(m_stForeignKeys, stFK)&#13;
	END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ConfigureRoutes: Configura as rotas REST automaticamente&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ConfigureRoutes()&#13;
sTablePath is string = m_sBasePath + “/” + Lower(m_sTableName)&#13;
&#13;
```&#13;
// GET /api/v1/table - Listar todos&#13;
WebAddRoute(sTablePath, httpGet, GetAllRecords)&#13;
&#13;
// GET /api/v1/table/{id} - Obter por ID&#13;
WebAddRoute(sTablePath + "/{id}", httpGet, GetRecordByID)&#13;
&#13;
// POST /api/v1/table - Criar novo&#13;
WebAddRoute(sTablePath, httpPost, CreateRecord)&#13;
&#13;
// PUT /api/v1/table/{id} - Atualizar&#13;
WebAddRoute(sTablePath + "/{id}", httpPut, UpdateRecord)&#13;
&#13;
// DELETE /api/v1/table/{id} - Excluir&#13;
WebAddRoute(sTablePath + "/{id}", httpDelete, DeleteRecord)&#13;
&#13;
// OPTIONS para CORS&#13;
WebAddRoute(sTablePath, httpOptions, HandleCORS)&#13;
WebAddRoute(sTablePath + "/{id}", httpOptions, HandleCORS)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetAllRecords: Retorna todos os registros (GET)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GetAllRecords()&#13;
sJSON is string&#13;
sFilter is string = “”&#13;
nLimit is int = 100&#13;
nOffset is int = 0&#13;
&#13;
```&#13;
// Configurar CORS&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	// Obtém parâmetros de consulta&#13;
	sFilter = WebParameter("filter", parameterGet)&#13;
	nLimit = Val(WebParameter("limit", parameterGet, "100"))&#13;
	nOffset = Val(WebParameter("offset", parameterGet, "0"))&#13;
	&#13;
	// Gera JSON com os registros&#13;
	sJSON = GenerateRecordsJSON(sFilter, nLimit, nOffset)&#13;
	&#13;
	// Retorna resposta&#13;
	WebWriteHTTPCode(200)&#13;
	WebWriteString(sJSON, "application/json")&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao obter registros", 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetRecordByID: Retorna registro específico por ID (GET)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GetRecordByID()&#13;
sID is string&#13;
sJSON is string&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	sID = WebParameter("id", parameterURL)&#13;
	&#13;
	IF sID = "" THEN&#13;
		HandleAPIError("ID não informado", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	// Busca o registro&#13;
	sJSON = FindRecordByID(sID)&#13;
	&#13;
	IF sJSON = "" THEN&#13;
		HandleAPIError("Registro não encontrado", 404)&#13;
	ELSE&#13;
		WebWriteHTTPCode(200)&#13;
		WebWriteString(sJSON, "application/json")&#13;
	END&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao buscar registro", 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CreateRecord: Cria novo registro (POST)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE CreateRecord()&#13;
sJSONInput is string&#13;
sResponse is string&#13;
varRecord is Variant&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	// Obtém JSON do body&#13;
	sJSONInput = WebReadRequestBody()&#13;
	&#13;
	IF sJSONInput = "" THEN&#13;
		HandleAPIError("Dados não informados", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	// Parse do JSON&#13;
	JSONToVariant(varRecord, sJSONInput)&#13;
	&#13;
	// Valida campos obrigatórios&#13;
	IF NOT ValidateMandatoryFields(varRecord) THEN&#13;
		RETURN // Erro já tratado na validação&#13;
	END&#13;
	&#13;
	// Cria o registro&#13;
	sResponse = InsertRecord(varRecord)&#13;
	&#13;
	WebWriteHTTPCode(201)&#13;
	WebWriteString(sResponse, "application/json")&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao criar registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// UpdateRecord: Atualiza registro existente (PUT)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE UpdateRecord()&#13;
sID is string&#13;
sJSONInput is string&#13;
sResponse is string&#13;
varRecord is Variant&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	sID = WebParameter("id", parameterURL)&#13;
	sJSONInput = WebReadRequestBody()&#13;
	&#13;
	IF sID = "" OR sJSONInput = "" THEN&#13;
		HandleAPIError("ID ou dados não informados", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	JSONToVariant(varRecord, sJSONInput)&#13;
	&#13;
	// Atualiza o registro&#13;
	sResponse = ModifyRecord(sID, varRecord)&#13;
	&#13;
	IF sResponse = "" THEN&#13;
		HandleAPIError("Registro não encontrado", 404)&#13;
	ELSE&#13;
		WebWriteHTTPCode(200)&#13;
		WebWriteString(sResponse, "application/json")&#13;
	END&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao atualizar registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// DeleteRecord: Exclui registro (DELETE)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE DeleteRecord()&#13;
sID is string&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	sID = WebParameter("id", parameterURL)&#13;
	&#13;
	IF sID = "" THEN&#13;
		HandleAPIError("ID não informado", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	IF RemoveRecord(sID) THEN&#13;
		WebWriteHTTPCode(204) // No Content&#13;
	ELSE&#13;
		HandleAPIError("Registro não encontrado", 404)&#13;
	END&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao excluir registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS DE MANIPULAÇÃO DE DADOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// GenerateRecordsJSON: Gera JSON com registros da tabela&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GenerateRecordsJSON(sFilter is string, nLimit is int, nOffset is int) : string&#13;
sJSON is string = “[”&#13;
bFirst is boolean = True&#13;
sFieldName is string&#13;
i is int&#13;
nCount is int = 0&#13;
&#13;
```&#13;
// Usa indirection para acessar dinamicamente a tabela&#13;
{m_sTableName, indFile}..Filter = sFilter&#13;
&#13;
HReadFirst({m_sTableName, indFile})&#13;
&#13;
WHILE NOT HOut({m_sTableName, indFile}) AND nCount &lt; nLimit&#13;
	IF nCount &gt;= nOffset THEN&#13;
		IF NOT bFirst THEN&#13;
			sJSON += ","&#13;
		END&#13;
		&#13;
		sJSON += "{"&#13;
		bFirst = True&#13;
		&#13;
		// Constrói JSON dinamicamente usando informações dos campos&#13;
		FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
			sFieldName = m_stFieldsInfo[i].sName&#13;
			&#13;
			IF NOT bFirst THEN&#13;
				sJSON += ","&#13;
			END&#13;
			&#13;
			sJSON += """" + sFieldName + """:"&#13;
			sJSON += FormatFieldValue(sFieldName, {m_sTableName + "." + sFieldName, indFile})&#13;
			&#13;
			bFirst = False&#13;
		END&#13;
		&#13;
		sJSON += "}"&#13;
		bFirst = False&#13;
	END&#13;
	&#13;
	nCount++&#13;
	HReadNext({m_sTableName, indFile})&#13;
END&#13;
&#13;
sJSON += "]"&#13;
RESULT sJSON&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// FindRecordByID: Busca registro por ID&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE FindRecordByID(sID is string) : string&#13;
sJSON is string = “”&#13;
sFieldName is string&#13;
i is int&#13;
&#13;
```&#13;
// Busca usando chave primária com indirection&#13;
IF HReadSeekFirst({m_sTableName, indFile}, {m_sPrimaryKey, indFile}, sID) THEN&#13;
	sJSON = "{"&#13;
	&#13;
	FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
		sFieldName = m_stFieldsInfo[i].sName&#13;
		&#13;
		IF i &gt; 1 THEN&#13;
			sJSON += ","&#13;
		END&#13;
		&#13;
		sJSON += """" + sFieldName + """:"&#13;
		sJSON += FormatFieldValue(sFieldName, {m_sTableName + "." + sFieldName, indFile})&#13;
	END&#13;
	&#13;
	sJSON += "}"&#13;
END&#13;
&#13;
RESULT sJSON&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// InsertRecord: Insere novo registro&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE InsertRecord(varRecord is Variant) : string&#13;
sFieldName is string&#13;
i is int&#13;
&#13;
```&#13;
// Limpa registro atual&#13;
HReset({m_sTableName, indFile})&#13;
&#13;
// Preenche campos usando indirection&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
	sFieldName = m_stFieldsInfo[i].sName&#13;
	&#13;
	IF VariantExists(varRecord, sFieldName) THEN&#13;
		{m_sTableName + "." + sFieldName, indFile} = varRecord[sFieldName]&#13;
	END&#13;
END&#13;
&#13;
// Insere o registro&#13;
IF HAdd({m_sTableName, indFile}) THEN&#13;
	// Retorna o registro inserido&#13;
	RESULT FindRecordByID({m_sTableName + "." + m_sPrimaryKey, indFile})&#13;
ELSE&#13;
	ExceptionThrow(2, "Erro ao inserir registro: " + HErrorInfo())&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ModifyRecord: Modifica registro existente&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ModifyRecord(sID is string, varRecord is Variant) : string&#13;
sFieldName is string&#13;
i is int&#13;
&#13;
```&#13;
// Busca o registro&#13;
IF NOT HReadSeekFirst({m_sTableName, indFile}, {m_sPrimaryKey, indFile}, sID) THEN&#13;
	RESULT ""&#13;
END&#13;
&#13;
// Atualiza campos&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
	sFieldName = m_stFieldsInfo[i].sName&#13;
	&#13;
	// Não atualiza chave primária&#13;
	IF sFieldName &lt;&gt; m_sPrimaryKey AND VariantExists(varRecord, sFieldName) THEN&#13;
		{m_sTableName + "." + sFieldName, indFile} = varRecord[sFieldName]&#13;
	END&#13;
END&#13;
&#13;
// Salva modificações&#13;
IF HModify({m_sTableName, indFile}) THEN&#13;
	RESULT FindRecordByID(sID)&#13;
ELSE&#13;
	ExceptionThrow(3, "Erro ao modificar registro: " + HErrorInfo())&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// RemoveRecord: Remove registro&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE RemoveRecord(sID is string) : boolean&#13;
IF HReadSeekFirst({m_sTableName, indFile}, {m_sPrimaryKey, indFile}, sID) THEN&#13;
RESULT HDelete({m_sTableName, indFile})&#13;
END&#13;
&#13;
```&#13;
RESULT False&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS AUXILIARES&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// FormatFieldValue: Formata valor do campo para JSON&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE FormatFieldValue(sFieldName is string, vValue is Variant) : string&#13;
nType is int&#13;
&#13;
```&#13;
// Obtém o tipo do campo&#13;
nType = HItemType(m_sTableName, sFieldName)&#13;
&#13;
SWITCH nType&#13;
	CASE hItemText, hItemMemo, hItemBinaryMemo&#13;
		RESULT """" + Replace(vValue, """", "\""") + """"&#13;
		&#13;
	CASE hItemDate&#13;
		IF vValue = "" OR vValue = "00000000" THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + DateToString(vValue, "YYYY-MM-DD") + """"&#13;
		END&#13;
		&#13;
	CASE hItemTime&#13;
		IF vValue = "" OR vValue = "0000" THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + TimeToString(vValue, "HH:MM:SS") + """"&#13;
		END&#13;
		&#13;
	CASE hItemDateTime&#13;
		IF vValue = "" THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + DateTimeToString(vValue, "YYYY-MM-DD HH:MM:SS") + """"&#13;
		END&#13;
		&#13;
	CASE hItemBoolean&#13;
		RESULT IIF(vValue, "true", "false")&#13;
		&#13;
	CASE hItemNumeric, hItemReal, hItemCurrency&#13;
		IF vValue = 0 OR vValue = "" THEN&#13;
			RESULT "0"&#13;
		ELSE&#13;
			RESULT Replace(vValue, ",", ".")&#13;
		END&#13;
		&#13;
	DEFAULT&#13;
		IF vValue = Null THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + vValue + """"&#13;
		END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ValidateMandatoryFields: Valida campos obrigatórios&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ValidateMandatoryFields(varRecord is Variant) : boolean&#13;
i is int&#13;
sField is string&#13;
sMissingFields is string = “”&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stMandatoryFields)&#13;
	sField = m_stMandatoryFields[i]&#13;
	&#13;
	IF NOT VariantExists(varRecord, sField) OR varRecord[sField] = "" THEN&#13;
		IF sMissingFields &lt;&gt; "" THEN&#13;
			sMissingFields += ", "&#13;
		END&#13;
		sMissingFields += sField&#13;
	END&#13;
END&#13;
&#13;
IF sMissingFields &lt;&gt; "" THEN&#13;
	HandleAPIError("Campos obrigatórios não informados: " + sMissingFields, 400)&#13;
	RESULT False&#13;
END&#13;
&#13;
RESULT True&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// SetCORSHeaders: Define headers CORS&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE SetCORSHeaders()&#13;
WebWriteHTTPHeader(“Access-Control-Allow-Origin”, “*”)&#13;
WebWriteHTTPHeader(“Access-Control-Allow-Methods”, “GET, POST, PUT, DELETE, OPTIONS”)&#13;
WebWriteHTTPHeader(“Access-Control-Allow-Headers”, “Content-Type, Authorization”)&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// HandleCORS: Trata requisições OPTIONS&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE HandleCORS()&#13;
SetCORSHeaders()&#13;
WebWriteHTTPCode(204)&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// HandleAPIError: Trata erros da API&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE HandleAPIError(sMessage is string, nCode is int = 500)&#13;
sErrorJSON is string&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
sErrorJSON = StringBuild("{\""error\"": {\""message\"": \""%1\"", \""code\"": %2}}", sMessage, nCode)&#13;
&#13;
WebWriteHTTPCode(nCode)&#13;
WebWriteString(sErrorJSON, "application/json")&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCRUDClient  &#13;
// Descrição: Classe cliente para consumir a API REST CRUD&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
CRestCRUDClient is Class&#13;
// Atributos privados&#13;
PRIVATE&#13;
m_sBaseURL is string&#13;
m_sTableName is string&#13;
m_sAuthToken is string&#13;
m_nTimeout is int = 30000&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PÚBLICOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa cliente da API&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sBaseURL is string, sTableName is string, sAuthToken is string = “”)&#13;
m_sBaseURL = sBaseURL&#13;
m_sTableName = sTableName&#13;
m_sAuthToken = sAuthToken&#13;
&#13;
```&#13;
// Remove barra final da URL se existir&#13;
IF Right(m_sBaseURL, 1) = "/" THEN&#13;
	m_sBaseURL = Left(m_sBaseURL, Length(m_sBaseURL) - 1)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetAll: Obtém todos os registros&#13;
//——————————————————————————&#13;
PROCEDURE GetAll(sFilter is string = “”, nLimit is int = 100, nOffset is int = 0) : Variant&#13;
sURL is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
// Constrói URL com parâmetros&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName)&#13;
&#13;
IF sFilter &lt;&gt; "" OR nLimit &lt;&gt; 100 OR nOffset &lt;&gt; 0 THEN&#13;
	sURL += "?"&#13;
	&#13;
	IF sFilter &lt;&gt; "" THEN&#13;
		sURL += "filter=" + URLEncode(sFilter) + "&amp;"&#13;
	END&#13;
	&#13;
	IF nLimit &lt;&gt; 100 THEN&#13;
		sURL += "limit=" + nLimit + "&amp;"&#13;
	END&#13;
	&#13;
	IF nOffset &lt;&gt; 0 THEN&#13;
		sURL += "offset=" + nOffset + "&amp;"&#13;
	END&#13;
	&#13;
	// Remove último &amp;&#13;
	sURL = Left(sURL, Length(sURL) - 1)&#13;
END&#13;
&#13;
// Faz requisição GET&#13;
sResponse = ExecuteHTTPRequest("GET", sURL)&#13;
&#13;
// Converte resposta para Variant&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetByID: Obtém registro por ID&#13;
//——————————————————————————&#13;
PROCEDURE GetByID(ID is string) : Variant&#13;
sURL is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName) + "/" + ID&#13;
&#13;
sResponse = ExecuteHTTPRequest("GET", sURL)&#13;
&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Create: Cria novo registro&#13;
//——————————————————————————&#13;
PROCEDURE Create(varData is Variant) : Variant&#13;
sURL is string&#13;
sJSON is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName)&#13;
&#13;
// Converte dados para JSON&#13;
sJSON = VariantToJSON(varData)&#13;
&#13;
// Faz requisição POST&#13;
sResponse = ExecuteHTTPRequest("POST", sURL, sJSON)&#13;
&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Update: Atualiza registro existente&#13;
//——————————————————————————&#13;
PROCEDURE Update(ID is string, varData is Variant) : Variant&#13;
sURL is string&#13;
sJSON is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName) + "/" + ID&#13;
&#13;
sJSON = VariantToJSON(varData)&#13;
&#13;
sResponse = ExecuteHTTPRequest("PUT", sURL, sJSON)&#13;
&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Delete: Exclui registro&#13;
//——————————————————————————&#13;
PROCEDURE Delete(ID is string) : boolean&#13;
sURL is string&#13;
sResponse is string&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName) + "/" + ID&#13;
&#13;
TRY&#13;
	sResponse = ExecuteHTTPRequest("DELETE", sURL)&#13;
	RESULT True&#13;
EXCEPTION&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// SetTimeout: Define timeout das requisições&#13;
//——————————————————————————&#13;
PROCEDURE SetTimeout(nTimeoutMS is int)&#13;
m_nTimeout = nTimeoutMS&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PRIVADOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// ExecuteHTTPRequest: Executa requisição HTTP&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ExecuteHTTPRequest(sMethod is string, sURL is string, sData is string = “”) : string&#13;
cHTTPRequest is httpRequest&#13;
cHTTPResponse is httpResponse&#13;
&#13;
```&#13;
// Configura requisição&#13;
cHTTPRequest.URL = sURL&#13;
cHTTPRequest.Method = sMethod&#13;
cHTTPRequest.Timeout = m_nTimeout&#13;
&#13;
// Headers padrão&#13;
cHTTPRequest.Header["Content-Type"] = "application/json"&#13;
cHTTPRequest.Header["Accept"] = "application/json"&#13;
&#13;
// Token de autenticação se informado&#13;
IF m_sAuthToken &lt;&gt; "" THEN&#13;
	cHTTPRequest.Header["Authorization"] = "Bearer " + m_sAuthToken&#13;
END&#13;
&#13;
// Dados para POST/PUT&#13;
IF sData &lt;&gt; "" THEN&#13;
	cHTTPRequest.Content = sData&#13;
END&#13;
&#13;
// Executa requisição&#13;
cHTTPResponse = HTTPSend(cHTTPRequest)&#13;
&#13;
// Verifica resposta&#13;
IF cHTTPResponse.StatusCode &gt;= 200 AND cHTTPResponse.StatusCode &lt; 300 THEN&#13;
	RESULT cHTTPResponse.Content&#13;
ELSE&#13;
	ExceptionThrow(cHTTPResponse.StatusCode, "Erro HTTP " + cHTTPResponse.StatusCode + ": " + cHTTPResponse.Content)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE USO DAS CLASSES&#13;
//==============================================================================&#13;
&#13;
// Exemplo servidor:&#13;
/*&#13;
// Cria servidor CRUD para tabela Cliente&#13;
crudServer is CRestCRUDServer(“Cliente”)&#13;
&#13;
// Inicia o servidor na porta 8080&#13;
crudServer.StartServer(8080, “/api/v1”)&#13;
*/&#13;
&#13;
// Exemplo cliente:&#13;
/*&#13;
// Cria cliente da API&#13;
clienteAPI is CRestCRUDClient(“http://localhost:8080/api/v1”, “cliente”)&#13;
&#13;
// Cria novo cliente&#13;
novoCliente is Variant&#13;
novoCliente.nome = “João Silva”&#13;
novoCliente.email = “joao@email.com”&#13;
novoCliente.telefone = “11999999999”&#13;
&#13;
clienteCriado is Variant = clienteAPI.Create(novoCliente)&#13;
Info(“Cliente criado com ID: “ + clienteCriado.id)&#13;
&#13;
// Busca todos os clientes&#13;
todosClientes is Variant = clienteAPI.GetAll()&#13;
Info(“Total de clientes: “ + ArrayCount(todosClientes))&#13;
&#13;
// Busca cliente por ID&#13;
cliente is Variant = clienteAPI.GetByID(“1”)&#13;
Info(“Nome do cliente: “ + cliente.nome)&#13;
&#13;
// Atualiza cliente&#13;
dadosAtualizacao is Variant&#13;
dadosAtualizacao.telefone = “11888888888”&#13;
clienteAtualizado is Variant = clienteAPI.Update(“1”, dadosAtualizacao)&#13;
&#13;
// Exclui cliente&#13;
IF clienteAPI.Delete(“1”) THEN&#13;
Info(“Cliente excluído com sucesso”)&#13;
END&#13;
*/&#13;
&#13;
[/code]&#13;
&#13;
Criei um sistema completo de CRUD REST API em WLanguage com duas classes principais:&#13;
CRestCRUDServer - Classe Servidor&#13;
Características principais:&#13;
	1.	Análise dinâmica da estrutura: Usa comandos H para analisar automaticamente qualquer tabela, view ou query SQL&#13;
	2.	Detecção automática de:&#13;
	•	Campos obrigatórios&#13;
	•	Chaves primárias&#13;
	•	Chaves estrangeiras&#13;
	•	Tipos de dados&#13;
	3.	Endpoints REST automáticos:&#13;
	•	GET /api/v1/tabela - Listar todos (com filtros, limit, offset)&#13;
	•	GET /api/v1/tabela/{id} - Buscar por ID&#13;
	•	POST /api/v1/tabela - Criar novo&#13;
	•	PUT /api/v1/tabela/{id} - Atualizar&#13;
	•	DELETE /api/v1/tabela/{id} - Excluir&#13;
	4.	Uso extensivo de indirection: Para acessar dinamicamente tabelas e campos&#13;
	5.	Manipulação JSON completa: Conversão automática entre registros HFSQL e JSON&#13;
	6.	Validação automática: Campos obrigatórios e tipos de dados&#13;
	7.	Suporte a CORS: Para integração web&#13;
CRestCRUDClient - Classe Cliente&#13;
Facilita o consumo da API:&#13;
	1.	Métodos simples: GetAll(), GetByID(), Create(), Update(), Delete()&#13;
	2.	Tratamento automático de JSON&#13;
	3.	Suporte a autenticação (Bearer token)&#13;
	4.	Timeout configurável&#13;
	5.	Tratamento de erros&#13;
Principais vantagens:&#13;
	•	Zero configuração: Apenas informe o nome da tab​​​​​​​​​​​​​​​​&#13;
&#13;
[code:wl]&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCRUDServer&#13;
// Descrição: Classe para gerar automaticamente CRUD REST API de qualquer tabela&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
CRestCRUDServer is Class&#13;
// Atributos privados&#13;
PRIVATE&#13;
m_sTableName is string&#13;
m_sConnectionString is string&#13;
m_stFieldsInfo is array of stFieldInfo&#13;
m_sPrimaryKey is string&#13;
m_stForeignKeys is array of stForeignKey&#13;
m_stMandatoryFields is array of string&#13;
m_nPort is int = 8080&#13;
m_sBasePath is string = “/api/v1”&#13;
END&#13;
&#13;
// Estruturas auxiliares&#13;
stFieldInfo is Structure&#13;
sName is string&#13;
nType is int&#13;
nSize is int&#13;
bMandatory is boolean&#13;
bPrimaryKey is boolean&#13;
sDefaultValue is string&#13;
END&#13;
&#13;
stForeignKey is Structure&#13;
sFieldName is string&#13;
sReferencedTable is string&#13;
sReferencedField is string&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PÚBLICOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa a classe com a tabela/view/query&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sTableOrQuery is string, sConnection is string = “”)&#13;
m_sTableName = sTableOrQuery&#13;
m_sConnectionString = sConnection&#13;
&#13;
```&#13;
// Analisa a estrutura da tabela/query&#13;
IF NOT AnalyzeTableStructure() THEN&#13;
	ExceptionThrow(1, "Erro ao analisar estrutura da tabela: " + m_sTableName)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// StartServer: Inicia o servidor REST&#13;
//——————————————————————————&#13;
PROCEDURE StartServer(nPort is int = 8080, sBasePath is string = “/api/v1”) : boolean&#13;
m_nPort = nPort&#13;
m_sBasePath = sBasePath&#13;
&#13;
```&#13;
// Configura as rotas REST automaticamente&#13;
ConfigureRoutes()&#13;
&#13;
// Inicia o servidor web&#13;
IF NOT WebServerStart(m_nPort) THEN&#13;
	RESULT False&#13;
END&#13;
&#13;
Info("Servidor REST iniciado na porta " + m_nPort + StringBuild(CR + "Endpoints disponíveis:" + CR + ...&#13;
	"GET %1/%2 - Listar todos" + CR + ...&#13;
	"GET %1/%2/{id} - Obter por ID" + CR + ...&#13;
	"POST %1/%2 - Criar novo" + CR + ...&#13;
	"PUT %1/%2/{id} - Atualizar" + CR + ...&#13;
	"DELETE %1/%2/{id} - Excluir", m_sBasePath, m_sTableName))&#13;
&#13;
RESULT True&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PRIVADOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeTableStructure: Analisa dinamicamente a estrutura da tabela&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeTableStructure() : boolean&#13;
stField is stFieldInfo&#13;
sAnalysis is string&#13;
nFieldCount is int&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
	// Obtém informações da análise&#13;
	sAnalysis = HListFile()&#13;
	&#13;
	// Verifica se a tabela existe na análise&#13;
	IF Position(sAnalysis, m_sTableName, firstRank, IgnoreCase) = 0 THEN&#13;
		// Tenta como query SQL&#13;
		IF NOT AnalyzeSQLQuery() THEN&#13;
			RESULT False&#13;
		END&#13;
	END&#13;
	&#13;
	// Obtém número de campos&#13;
	nFieldCount = HNbItem(m_sTableName)&#13;
	&#13;
	// Analisa cada campo&#13;
	FOR i = 1 TO nFieldCount&#13;
		stField.sName = HListItem(m_sTableName, i)&#13;
		stField.nType = HItemType(m_sTableName, stField.sName)&#13;
		stField.nSize = HItemSize(m_sTableName, stField.sName)&#13;
		stField.bMandatory = HItemIsMandatory(m_sTableName, stField.sName)&#13;
		stField.bPrimaryKey = HItemIsKey(m_sTableName, stField.sName)&#13;
		&#13;
		// Adiciona ao array de campos&#13;
		ArrayAdd(m_stFieldsInfo, stField)&#13;
		&#13;
		// Identifica chave primária&#13;
		IF stField.bPrimaryKey THEN&#13;
			m_sPrimaryKey = stField.sName&#13;
		END&#13;
		&#13;
		// Adiciona campos obrigatórios&#13;
		IF stField.bMandatory THEN&#13;
			ArrayAdd(m_stMandatoryFields, stField.sName)&#13;
		END&#13;
	END&#13;
	&#13;
	// Analisa chaves estrangeiras&#13;
	AnalyzeForeignKeys()&#13;
	&#13;
	RESULT True&#13;
	&#13;
EXCEPTION&#13;
	Error("Erro ao analisar estrutura: " + ExceptionInfo())&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeSQLQuery: Analisa query SQL personalizada&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeSQLQuery() : boolean&#13;
qryTemp is Data Source&#13;
&#13;
```&#13;
TRY&#13;
	// Executa a query para obter estrutura&#13;
	IF HExecuteSQLQuery(qryTemp, m_sConnectionString, m_sTableName) THEN&#13;
		// Usa HDescribeFile para obter estrutura da query&#13;
		HDescribeFile(qryTemp)&#13;
		m_sTableName = qryTemp..Name&#13;
		RESULT True&#13;
	END&#13;
	&#13;
	RESULT False&#13;
	&#13;
EXCEPTION&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeForeignKeys: Identifica chaves estrangeiras&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeForeignKeys()&#13;
stFK is stForeignKey&#13;
i is int&#13;
sLinkInfo is string&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
	// Verifica se o campo é uma chave estrangeira&#13;
	sLinkInfo = HListLink(m_sTableName, m_stFieldsInfo[i].sName)&#13;
	&#13;
	IF sLinkInfo &lt;&gt; "" THEN&#13;
		stFK.sFieldName = m_stFieldsInfo[i].sName&#13;
		// Parse das informações de link (simplificado)&#13;
		stFK.sReferencedTable = ExtractString(sLinkInfo, 1, TAB)&#13;
		stFK.sReferencedField = ExtractString(sLinkInfo, 2, TAB)&#13;
		&#13;
		ArrayAdd(m_stForeignKeys, stFK)&#13;
	END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ConfigureRoutes: Configura as rotas REST automaticamente&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ConfigureRoutes()&#13;
sTablePath is string = m_sBasePath + “/” + Lower(m_sTableName)&#13;
&#13;
```&#13;
// GET /api/v1/table - Listar todos&#13;
WebAddRoute(sTablePath, httpGet, GetAllRecords)&#13;
&#13;
// GET /api/v1/table/{id} - Obter por ID&#13;
WebAddRoute(sTablePath + "/{id}", httpGet, GetRecordByID)&#13;
&#13;
// POST /api/v1/table - Criar novo&#13;
WebAddRoute(sTablePath, httpPost, CreateRecord)&#13;
&#13;
// PUT /api/v1/table/{id} - Atualizar&#13;
WebAddRoute(sTablePath + "/{id}", httpPut, UpdateRecord)&#13;
&#13;
// DELETE /api/v1/table/{id} - Excluir&#13;
WebAddRoute(sTablePath + "/{id}", httpDelete, DeleteRecord)&#13;
&#13;
// OPTIONS para CORS&#13;
WebAddRoute(sTablePath, httpOptions, HandleCORS)&#13;
WebAddRoute(sTablePath + "/{id}", httpOptions, HandleCORS)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetAllRecords: Retorna todos os registros (GET)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GetAllRecords()&#13;
sJSON is string&#13;
sFilter is string = “”&#13;
nLimit is int = 100&#13;
nOffset is int = 0&#13;
&#13;
```&#13;
// Configurar CORS&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	// Obtém parâmetros de consulta&#13;
	sFilter = WebParameter("filter", parameterGet)&#13;
	nLimit = Val(WebParameter("limit", parameterGet, "100"))&#13;
	nOffset = Val(WebParameter("offset", parameterGet, "0"))&#13;
	&#13;
	// Gera JSON com os registros&#13;
	sJSON = GenerateRecordsJSON(sFilter, nLimit, nOffset)&#13;
	&#13;
	// Retorna resposta&#13;
	WebWriteHTTPCode(200)&#13;
	WebWriteString(sJSON, "application/json")&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao obter registros", 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetRecordByID: Retorna registro específico por ID (GET)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GetRecordByID()&#13;
sID is string&#13;
sJSON is string&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	sID = WebParameter("id", parameterURL)&#13;
	&#13;
	IF sID = "" THEN&#13;
		HandleAPIError("ID não informado", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	// Busca o registro&#13;
	sJSON = FindRecordByID(sID)&#13;
	&#13;
	IF sJSON = "" THEN&#13;
		HandleAPIError("Registro não encontrado", 404)&#13;
	ELSE&#13;
		WebWriteHTTPCode(200)&#13;
		WebWriteString(sJSON, "application/json")&#13;
	END&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao buscar registro", 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CreateRecord: Cria novo registro (POST)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE CreateRecord()&#13;
sJSONInput is string&#13;
sResponse is string&#13;
varRecord is Variant&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	// Obtém JSON do body&#13;
	sJSONInput = WebReadRequestBody()&#13;
	&#13;
	IF sJSONInput = "" THEN&#13;
		HandleAPIError("Dados não informados", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	// Parse do JSON&#13;
	JSONToVariant(varRecord, sJSONInput)&#13;
	&#13;
	// Valida campos obrigatórios&#13;
	IF NOT ValidateMandatoryFields(varRecord) THEN&#13;
		RETURN // Erro já tratado na validação&#13;
	END&#13;
	&#13;
	// Cria o registro&#13;
	sResponse = InsertRecord(varRecord)&#13;
	&#13;
	WebWriteHTTPCode(201)&#13;
	WebWriteString(sResponse, "application/json")&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao criar registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// UpdateRecord: Atualiza registro existente (PUT)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE UpdateRecord()&#13;
sID is string&#13;
sJSONInput is string&#13;
sResponse is string&#13;
varRecord is Variant&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	sID = WebParameter("id", parameterURL)&#13;
	sJSONInput = WebReadRequestBody()&#13;
	&#13;
	IF sID = "" OR sJSONInput = "" THEN&#13;
		HandleAPIError("ID ou dados não informados", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	JSONToVariant(varRecord, sJSONInput)&#13;
	&#13;
	// Atualiza o registro&#13;
	sResponse = ModifyRecord(sID, varRecord)&#13;
	&#13;
	IF sResponse = "" THEN&#13;
		HandleAPIError("Registro não encontrado", 404)&#13;
	ELSE&#13;
		WebWriteHTTPCode(200)&#13;
		WebWriteString(sResponse, "application/json")&#13;
	END&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao atualizar registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// DeleteRecord: Exclui registro (DELETE)&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE DeleteRecord()&#13;
sID is string&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
TRY&#13;
	sID = WebParameter("id", parameterURL)&#13;
	&#13;
	IF sID = "" THEN&#13;
		HandleAPIError("ID não informado", 400)&#13;
		RETURN&#13;
	END&#13;
	&#13;
	IF RemoveRecord(sID) THEN&#13;
		WebWriteHTTPCode(204) // No Content&#13;
	ELSE&#13;
		HandleAPIError("Registro não encontrado", 404)&#13;
	END&#13;
	&#13;
EXCEPTION&#13;
	HandleAPIError("Erro ao excluir registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS DE MANIPULAÇÃO DE DADOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// GenerateRecordsJSON: Gera JSON com registros da tabela&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GenerateRecordsJSON(sFilter is string, nLimit is int, nOffset is int) : string&#13;
sJSON is string = “[”&#13;
bFirst is boolean = True&#13;
sFieldName is string&#13;
i is int&#13;
nCount is int = 0&#13;
&#13;
```&#13;
// Usa indirection para acessar dinamicamente a tabela&#13;
{m_sTableName, indFile}..Filter = sFilter&#13;
&#13;
HReadFirst({m_sTableName, indFile})&#13;
&#13;
WHILE NOT HOut({m_sTableName, indFile}) AND nCount &lt; nLimit&#13;
	IF nCount &gt;= nOffset THEN&#13;
		IF NOT bFirst THEN&#13;
			sJSON += ","&#13;
		END&#13;
		&#13;
		sJSON += "{"&#13;
		bFirst = True&#13;
		&#13;
		// Constrói JSON dinamicamente usando informações dos campos&#13;
		FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
			sFieldName = m_stFieldsInfo[i].sName&#13;
			&#13;
			IF NOT bFirst THEN&#13;
				sJSON += ","&#13;
			END&#13;
			&#13;
			sJSON += """" + sFieldName + """:"&#13;
			sJSON += FormatFieldValue(sFieldName, {m_sTableName + "." + sFieldName, indFile})&#13;
			&#13;
			bFirst = False&#13;
		END&#13;
		&#13;
		sJSON += "}"&#13;
		bFirst = False&#13;
	END&#13;
	&#13;
	nCount++&#13;
	HReadNext({m_sTableName, indFile})&#13;
END&#13;
&#13;
sJSON += "]"&#13;
RESULT sJSON&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// FindRecordByID: Busca registro por ID&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE FindRecordByID(sID is string) : string&#13;
sJSON is string = “”&#13;
sFieldName is string&#13;
i is int&#13;
&#13;
```&#13;
// Busca usando chave primária com indirection&#13;
IF HReadSeekFirst({m_sTableName, indFile}, {m_sPrimaryKey, indFile}, sID) THEN&#13;
	sJSON = "{"&#13;
	&#13;
	FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
		sFieldName = m_stFieldsInfo[i].sName&#13;
		&#13;
		IF i &gt; 1 THEN&#13;
			sJSON += ","&#13;
		END&#13;
		&#13;
		sJSON += """" + sFieldName + """:"&#13;
		sJSON += FormatFieldValue(sFieldName, {m_sTableName + "." + sFieldName, indFile})&#13;
	END&#13;
	&#13;
	sJSON += "}"&#13;
END&#13;
&#13;
RESULT sJSON&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// InsertRecord: Insere novo registro&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE InsertRecord(varRecord is Variant) : string&#13;
sFieldName is string&#13;
i is int&#13;
&#13;
```&#13;
// Limpa registro atual&#13;
HReset({m_sTableName, indFile})&#13;
&#13;
// Preenche campos usando indirection&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
	sFieldName = m_stFieldsInfo[i].sName&#13;
	&#13;
	IF VariantExists(varRecord, sFieldName) THEN&#13;
		{m_sTableName + "." + sFieldName, indFile} = varRecord[sFieldName]&#13;
	END&#13;
END&#13;
&#13;
// Insere o registro&#13;
IF HAdd({m_sTableName, indFile}) THEN&#13;
	// Retorna o registro inserido&#13;
	RESULT FindRecordByID({m_sTableName + "." + m_sPrimaryKey, indFile})&#13;
ELSE&#13;
	ExceptionThrow(2, "Erro ao inserir registro: " + HErrorInfo())&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ModifyRecord: Modifica registro existente&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ModifyRecord(sID is string, varRecord is Variant) : string&#13;
sFieldName is string&#13;
i is int&#13;
&#13;
```&#13;
// Busca o registro&#13;
IF NOT HReadSeekFirst({m_sTableName, indFile}, {m_sPrimaryKey, indFile}, sID) THEN&#13;
	RESULT ""&#13;
END&#13;
&#13;
// Atualiza campos&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
	sFieldName = m_stFieldsInfo[i].sName&#13;
	&#13;
	// Não atualiza chave primária&#13;
	IF sFieldName &lt;&gt; m_sPrimaryKey AND VariantExists(varRecord, sFieldName) THEN&#13;
		{m_sTableName + "." + sFieldName, indFile} = varRecord[sFieldName]&#13;
	END&#13;
END&#13;
&#13;
// Salva modificações&#13;
IF HModify({m_sTableName, indFile}) THEN&#13;
	RESULT FindRecordByID(sID)&#13;
ELSE&#13;
	ExceptionThrow(3, "Erro ao modificar registro: " + HErrorInfo())&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// RemoveRecord: Remove registro&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE RemoveRecord(sID is string) : boolean&#13;
IF HReadSeekFirst({m_sTableName, indFile}, {m_sPrimaryKey, indFile}, sID) THEN&#13;
RESULT HDelete({m_sTableName, indFile})&#13;
END&#13;
&#13;
```&#13;
RESULT False&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS AUXILIARES&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// FormatFieldValue: Formata valor do campo para JSON&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE FormatFieldValue(sFieldName is string, vValue is Variant) : string&#13;
nType is int&#13;
&#13;
```&#13;
// Obtém o tipo do campo&#13;
nType = HItemType(m_sTableName, sFieldName)&#13;
&#13;
SWITCH nType&#13;
	CASE hItemText, hItemMemo, hItemBinaryMemo&#13;
		RESULT """" + Replace(vValue, """", "\""") + """"&#13;
		&#13;
	CASE hItemDate&#13;
		IF vValue = "" OR vValue = "00000000" THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + DateToString(vValue, "YYYY-MM-DD") + """"&#13;
		END&#13;
		&#13;
	CASE hItemTime&#13;
		IF vValue = "" OR vValue = "0000" THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + TimeToString(vValue, "HH:MM:SS") + """"&#13;
		END&#13;
		&#13;
	CASE hItemDateTime&#13;
		IF vValue = "" THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + DateTimeToString(vValue, "YYYY-MM-DD HH:MM:SS") + """"&#13;
		END&#13;
		&#13;
	CASE hItemBoolean&#13;
		RESULT IIF(vValue, "true", "false")&#13;
		&#13;
	CASE hItemNumeric, hItemReal, hItemCurrency&#13;
		IF vValue = 0 OR vValue = "" THEN&#13;
			RESULT "0"&#13;
		ELSE&#13;
			RESULT Replace(vValue, ",", ".")&#13;
		END&#13;
		&#13;
	DEFAULT&#13;
		IF vValue = Null THEN&#13;
			RESULT "null"&#13;
		ELSE&#13;
			RESULT """" + vValue + """"&#13;
		END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ValidateMandatoryFields: Valida campos obrigatórios&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ValidateMandatoryFields(varRecord is Variant) : boolean&#13;
i is int&#13;
sField is string&#13;
sMissingFields is string = “”&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stMandatoryFields)&#13;
	sField = m_stMandatoryFields[i]&#13;
	&#13;
	IF NOT VariantExists(varRecord, sField) OR varRecord[sField] = "" THEN&#13;
		IF sMissingFields &lt;&gt; "" THEN&#13;
			sMissingFields += ", "&#13;
		END&#13;
		sMissingFields += sField&#13;
	END&#13;
END&#13;
&#13;
IF sMissingFields &lt;&gt; "" THEN&#13;
	HandleAPIError("Campos obrigatórios não informados: " + sMissingFields, 400)&#13;
	RESULT False&#13;
END&#13;
&#13;
RESULT True&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// SetCORSHeaders: Define headers CORS&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE SetCORSHeaders()&#13;
WebWriteHTTPHeader(“Access-Control-Allow-Origin”, “*”)&#13;
WebWriteHTTPHeader(“Access-Control-Allow-Methods”, “GET, POST, PUT, DELETE, OPTIONS”)&#13;
WebWriteHTTPHeader(“Access-Control-Allow-Headers”, “Content-Type, Authorization”)&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// HandleCORS: Trata requisições OPTIONS&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE HandleCORS()&#13;
SetCORSHeaders()&#13;
WebWriteHTTPCode(204)&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// HandleAPIError: Trata erros da API&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE HandleAPIError(sMessage is string, nCode is int = 500)&#13;
sErrorJSON is string&#13;
&#13;
```&#13;
SetCORSHeaders()&#13;
&#13;
sErrorJSON = StringBuild("{\""error\"": {\""message\"": \""%1\"", \""code\"": %2}}", sMessage, nCode)&#13;
&#13;
WebWriteHTTPCode(nCode)&#13;
WebWriteString(sErrorJSON, "application/json")&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCRUDClient  &#13;
// Descrição: Classe cliente para consumir a API REST CRUD&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
CRestCRUDClient is Class&#13;
// Atributos privados&#13;
PRIVATE&#13;
m_sBaseURL is string&#13;
m_sTableName is string&#13;
m_sAuthToken is string&#13;
m_nTimeout is int = 30000&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PÚBLICOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa cliente da API&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sBaseURL is string, sTableName is string, sAuthToken is string = “”)&#13;
m_sBaseURL = sBaseURL&#13;
m_sTableName = sTableName&#13;
m_sAuthToken = sAuthToken&#13;
&#13;
```&#13;
// Remove barra final da URL se existir&#13;
IF Right(m_sBaseURL, 1) = "/" THEN&#13;
	m_sBaseURL = Left(m_sBaseURL, Length(m_sBaseURL) - 1)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetAll: Obtém todos os registros&#13;
//——————————————————————————&#13;
PROCEDURE GetAll(sFilter is string = “”, nLimit is int = 100, nOffset is int = 0) : Variant&#13;
sURL is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
// Constrói URL com parâmetros&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName)&#13;
&#13;
IF sFilter &lt;&gt; "" OR nLimit &lt;&gt; 100 OR nOffset &lt;&gt; 0 THEN&#13;
	sURL += "?"&#13;
	&#13;
	IF sFilter &lt;&gt; "" THEN&#13;
		sURL += "filter=" + URLEncode(sFilter) + "&amp;"&#13;
	END&#13;
	&#13;
	IF nLimit &lt;&gt; 100 THEN&#13;
		sURL += "limit=" + nLimit + "&amp;"&#13;
	END&#13;
	&#13;
	IF nOffset &lt;&gt; 0 THEN&#13;
		sURL += "offset=" + nOffset + "&amp;"&#13;
	END&#13;
	&#13;
	// Remove último &amp;&#13;
	sURL = Left(sURL, Length(sURL) - 1)&#13;
END&#13;
&#13;
// Faz requisição GET&#13;
sResponse = ExecuteHTTPRequest("GET", sURL)&#13;
&#13;
// Converte resposta para Variant&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetByID: Obtém registro por ID&#13;
//——————————————————————————&#13;
PROCEDURE GetByID(ID is string) : Variant&#13;
sURL is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName) + "/" + ID&#13;
&#13;
sResponse = ExecuteHTTPRequest("GET", sURL)&#13;
&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Create: Cria novo registro&#13;
//——————————————————————————&#13;
PROCEDURE Create(varData is Variant) : Variant&#13;
sURL is string&#13;
sJSON is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName)&#13;
&#13;
// Converte dados para JSON&#13;
sJSON = VariantToJSON(varData)&#13;
&#13;
// Faz requisição POST&#13;
sResponse = ExecuteHTTPRequest("POST", sURL, sJSON)&#13;
&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Update: Atualiza registro existente&#13;
//——————————————————————————&#13;
PROCEDURE Update(ID is string, varData is Variant) : Variant&#13;
sURL is string&#13;
sJSON is string&#13;
sResponse is string&#13;
varResult is Variant&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName) + "/" + ID&#13;
&#13;
sJSON = VariantToJSON(varData)&#13;
&#13;
sResponse = ExecuteHTTPRequest("PUT", sURL, sJSON)&#13;
&#13;
JSONToVariant(varResult, sResponse)&#13;
&#13;
RESULT varResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Delete: Exclui registro&#13;
//——————————————————————————&#13;
PROCEDURE Delete(ID is string) : boolean&#13;
sURL is string&#13;
sResponse is string&#13;
&#13;
```&#13;
sURL = m_sBaseURL + "/" + Lower(m_sTableName) + "/" + ID&#13;
&#13;
TRY&#13;
	sResponse = ExecuteHTTPRequest("DELETE", sURL)&#13;
	RESULT True&#13;
EXCEPTION&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// SetTimeout: Define timeout das requisições&#13;
//——————————————————————————&#13;
PROCEDURE SetTimeout(nTimeoutMS is int)&#13;
m_nTimeout = nTimeoutMS&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS PRIVADOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// ExecuteHTTPRequest: Executa requisição HTTP&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ExecuteHTTPRequest(sMethod is string, sURL is string, sData is string = “”) : string&#13;
cHTTPRequest is httpRequest&#13;
cHTTPResponse is httpResponse&#13;
&#13;
```&#13;
// Configura requisição&#13;
cHTTPRequest.URL = sURL&#13;
cHTTPRequest.Method = sMethod&#13;
cHTTPRequest.Timeout = m_nTimeout&#13;
&#13;
// Headers padrão&#13;
cHTTPRequest.Header["Content-Type"] = "application/json"&#13;
cHTTPRequest.Header["Accept"] = "application/json"&#13;
&#13;
// Token de autenticação se informado&#13;
IF m_sAuthToken &lt;&gt; "" THEN&#13;
	cHTTPRequest.Header["Authorization"] = "Bearer " + m_sAuthToken&#13;
END&#13;
&#13;
// Dados para POST/PUT&#13;
IF sData &lt;&gt; "" THEN&#13;
	cHTTPRequest.Content = sData&#13;
END&#13;
&#13;
// Executa requisição&#13;
cHTTPResponse = HTTPSend(cHTTPRequest)&#13;
&#13;
// Verifica resposta&#13;
IF cHTTPResponse.StatusCode &gt;= 200 AND cHTTPResponse.StatusCode &lt; 300 THEN&#13;
	RESULT cHTTPResponse.Content&#13;
ELSE&#13;
	ExceptionThrow(cHTTPResponse.StatusCode, "Erro HTTP " + cHTTPResponse.StatusCode + ": " + cHTTPResponse.Content)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE USO DAS CLASSES&#13;
//==============================================================================&#13;
&#13;
// Exemplo servidor:&#13;
/*&#13;
// Cria servidor CRUD para tabela Cliente&#13;
crudServer is CRestCRUDServer(“Cliente”)&#13;
&#13;
// Inicia o servidor na porta 8080&#13;
crudServer.StartServer(8080, “/api/v1”)&#13;
*/&#13;
&#13;
// Exemplo cliente:&#13;
/*&#13;
// Cria cliente da API&#13;
clienteAPI is CRestCRUDClient(“http://localhost:8080/api/v1”, “cliente”)&#13;
&#13;
// Cria novo cliente&#13;
novoCliente is Variant&#13;
novoCliente.nome = “João Silva”&#13;
novoCliente.email = “joao@email.com”&#13;
novoCliente.telefone = “11999999999”&#13;
&#13;
clienteCriado is Variant = clienteAPI.Create(novoCliente)&#13;
Info(“Cliente criado com ID: “ + clienteCriado.id)&#13;
&#13;
// Busca todos os clientes&#13;
todosClientes is Variant = clienteAPI.GetAll()&#13;
Info(“Total de clientes: “ + ArrayCount(todosClientes))&#13;
&#13;
// Busca cliente por ID&#13;
cliente is Variant = clienteAPI.GetByID(“1”)&#13;
Info(“Nome do cliente: “ + cliente.nome)&#13;
&#13;
// Atualiza cliente&#13;
dadosAtualizacao is Variant&#13;
dadosAtualizacao.telefone = “11888888888”&#13;
clienteAtualizado is Variant = clienteAPI.Update(“1”, dadosAtualizacao)&#13;
&#13;
// Exclui cliente&#13;
IF clienteAPI.Delete(“1”) THEN&#13;
Info(“Cliente excluído com sucesso”)&#13;
END&#13;
*/&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCRUDManager&#13;
// Descrição: Gerenciador avançado para múltiplas tabelas e relacionamentos&#13;
// Autor: Sistema Automático  &#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
CRestCRUDManager is Class&#13;
PRIVATE&#13;
m_stServers is array of stServerInfo&#13;
m_stRelationships is array of stTableRelation&#13;
m_sConnectionString is string&#13;
m_nBasePort is int = 8080&#13;
END&#13;
&#13;
// Estruturas para gerenciamento avançado&#13;
stServerInfo is Structure&#13;
sTableName is string&#13;
cServer is CRestCRUDServer&#13;
nPort is int&#13;
sBasePath is string&#13;
bActive is boolean&#13;
END&#13;
&#13;
stTableRelation is Structure&#13;
sParentTable is string&#13;
sChildTable is string&#13;
sParentField is string&#13;
sChildField is string&#13;
sRelationType is string // “one-to-many”, “many-to-one”, “many-to-many”&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS DO GERENCIADOR AVANÇADO&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa gerenciador&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sConnection is string = “”, nBasePort is int = 8080)&#13;
m_sConnectionString = sConnection&#13;
m_nBasePort = nBasePort&#13;
&#13;
```&#13;
// Auto-descobre todas as tabelas do projeto&#13;
AutoDiscoverTables()&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AutoDiscoverTables: Descobre automaticamente todas as tabelas&#13;
//——————————————————————————&#13;
PROCEDURE AutoDiscoverTables()&#13;
sTables is string&#13;
sTable is string&#13;
nPos is int = 1&#13;
stServer is stServerInfo&#13;
&#13;
```&#13;
// Lista todas as tabelas da análise&#13;
sTables = HListFile()&#13;
&#13;
WHILE nPos &lt;= Length(sTables)&#13;
	sTable = ExtractString(sTables, nPos, CR)&#13;
	&#13;
	IF sTable &lt;&gt; "" AND Left(sTable, 1) &lt;&gt; "." THEN&#13;
		// Cria servidor para cada tabela&#13;
		stServer.sTableName = sTable&#13;
		stServer.cServer = new CRestCRUDServer(sTable, m_sConnectionString)&#13;
		stServer.nPort = m_nBasePort + ArrayCount(m_stServers)&#13;
		stServer.sBasePath = "/api/v1"&#13;
		stServer.bActive = False&#13;
		&#13;
		ArrayAdd(m_stServers, stServer)&#13;
	END&#13;
	&#13;
	nPos++&#13;
END&#13;
&#13;
// Analisa relacionamentos&#13;
AnalyzeAllRelationships()&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// StartAllServers: Inicia servidores para todas as tabelas&#13;
//——————————————————————————&#13;
PROCEDURE StartAllServers() : boolean&#13;
i is int&#13;
bSuccess is boolean = True&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stServers)&#13;
	IF m_stServers[i].cServer.StartServer(m_stServers[i].nPort, m_stServers[i].sBasePath) THEN&#13;
		m_stServers[i].bActive = True&#13;
		Trace("Servidor iniciado: " + m_stServers[i].sTableName + " na porta " + m_stServers[i].nPort)&#13;
	ELSE&#13;
		bSuccess = False&#13;
		Error("Falha ao iniciar servidor para: " + m_stServers[i].sTableName)&#13;
	END&#13;
END&#13;
&#13;
RESULT bSuccess&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// StartServerForTable: Inicia servidor específico para uma tabela&#13;
//——————————————————————————&#13;
PROCEDURE StartServerForTable(sTableName is string) : boolean&#13;
i is int&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stServers)&#13;
	IF Upper(m_stServers[i].sTableName) = Upper(sTableName) THEN&#13;
		IF m_stServers[i].cServer.StartServer(m_stServers[i].nPort, m_stServers[i].sBasePath) THEN&#13;
			m_stServers[i].bActive = True&#13;
			RESULT True&#13;
		END&#13;
	END&#13;
END&#13;
&#13;
RESULT False&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetAPIDocumentation: Gera documentação automática da API&#13;
//——————————————————————————&#13;
PROCEDURE GetAPIDocumentation() : string&#13;
sDoc is string&#13;
i is int&#13;
j is int&#13;
&#13;
```&#13;
sDoc = "# API REST - Documentação Automática" + CR + CR&#13;
sDoc += "## Tabelas Disponíveis:" + CR + CR&#13;
&#13;
FOR i = 1 TO ArrayCount(m_stServers)&#13;
	sDoc += "### " + m_stServers[i].sTableName + CR&#13;
	sDoc += "**Porta:** " + m_stServers[i].nPort + CR&#13;
	sDoc += "**Status:** " + IIF(m_stServers[i].bActive, "Ativo", "Inativo") + CR&#13;
	sDoc += "**Base URL:** http://localhost:" + m_stServers[i].nPort + m_stServers[i].sBasePath + CR + CR&#13;
	&#13;
	sDoc += "**Endpoints:**" + CR&#13;
	sDoc += "- GET    /" + Lower(m_stServers[i].sTableName) + " - Listar todos" + CR&#13;
	sDoc += "- GET    /" + Lower(m_stServers[i].sTableName) + "/{id} - Obter por ID" + CR&#13;
	sDoc += "- POST   /" + Lower(m_stServers[i].sTableName) + " - Criar novo" + CR&#13;
	sDoc += "- PUT    /" + Lower(m_stServers[i].sTableName) + "/{id} - Atualizar" + CR&#13;
	sDoc += "- DELETE /" + Lower(m_stServers[i].sTableName) + "/{id} - Excluir" + CR + CR&#13;
END&#13;
&#13;
// Adiciona informações sobre relacionamentos&#13;
IF ArrayCount(m_stRelationships) &gt; 0 THEN&#13;
	sDoc += "## Relacionamentos Detectados:" + CR + CR&#13;
	&#13;
	FOR j = 1 TO ArrayCount(m_stRelationships)&#13;
		sDoc += "- " + m_stRelationships[j].sParentTable + "." + m_stRelationships[j].sParentField&#13;
		sDoc += " → " + m_stRelationships[j].sChildTable + "." + m_stRelationships[j].sChildField&#13;
		sDoc += " (" + m_stRelationships[j].sRelationType + ")" + CR&#13;
	END&#13;
END&#13;
&#13;
RESULT sDoc&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AnalyzeAllRelationships: Analisa relacionamentos entre tabelas&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AnalyzeAllRelationships()&#13;
i is int&#13;
j is int&#13;
sLinks is string&#13;
sLink is string&#13;
nLinkPos is int&#13;
stRelation is stTableRelation&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stServers)&#13;
	sLinks = HListLink(m_stServers[i].sTableName)&#13;
	nLinkPos = 1&#13;
	&#13;
	WHILE nLinkPos &lt;= Length(sLinks)&#13;
		sLink = ExtractString(sLinks, nLinkPos, CR)&#13;
		&#13;
		IF sLink &lt;&gt; "" THEN&#13;
			// Parse do relacionamento&#13;
			stRelation.sChildTable = m_stServers[i].sTableName&#13;
			stRelation.sChildField = ExtractString(sLink, 1, TAB)&#13;
			stRelation.sParentTable = ExtractString(sLink, 2, TAB)&#13;
			stRelation.sParentField = ExtractString(sLink, 3, TAB)&#13;
			stRelation.sRelationType = "many-to-one"&#13;
			&#13;
			ArrayAdd(m_stRelationships, stRelation)&#13;
		END&#13;
		&#13;
		nLinkPos++&#13;
	END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCRUDHelper&#13;
// Descrição: Utilitários e helpers para facilitar desenvolvimento&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
CRestCRUDHelper is Class&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS ESTÁTICOS UTILITÁRIOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// GenerateClientCode: Gera código cliente automaticamente&#13;
//——————————————————————————&#13;
PROCEDURE STATIC GenerateClientCode(sTableName is string, sBaseURL is string) : string&#13;
sCode is string&#13;
sClassName is string&#13;
&#13;
```&#13;
sClassName = "C" + sTableName + "API"&#13;
&#13;
sCode = "// Classe cliente gerada automaticamente para " + sTableName + CR&#13;
sCode += sClassName + " is Class" + CR&#13;
sCode += "	PRIVATE" + CR&#13;
sCode += "		m_cClient is CRestCRUDClient" + CR&#13;
sCode += "END" + CR + CR&#13;
&#13;
sCode += "PROCEDURE Constructor()" + CR&#13;
sCode += "	m_cClient = new CRestCRUDClient(""" + sBaseURL + """, """ + Lower(sTableName) + """)" + CR&#13;
sCode += "END" + CR + CR&#13;
&#13;
// Métodos específicos para a tabela&#13;
sCode += "PROCEDURE List" + sTableName + "(sFilter is string = """") : Variant" + CR&#13;
sCode += "	RESULT m_cClient.GetAll(sFilter)" + CR&#13;
sCode += "END" + CR + CR&#13;
&#13;
sCode += "PROCEDURE Get" + sTableName + "(ID is string) : Variant" + CR&#13;
sCode += "	RESULT m_cClient.GetByID(ID)" + CR&#13;
sCode += "END" + CR + CR&#13;
&#13;
sCode += "PROCEDURE Create" + sTableName + "(var" + sTableName + " is Variant) : Variant" + CR&#13;
sCode += "	RESULT m_cClient.Create(var" + sTableName + ")" + CR&#13;
sCode += "END" + CR + CR&#13;
&#13;
sCode += "PROCEDURE Update" + sTableName + "(ID is string, var" + sTableName + " is Variant) : Variant" + CR&#13;
sCode += "	RESULT m_cClient.Update(ID, var" + sTableName + ")" + CR&#13;
sCode += "END" + CR + CR&#13;
&#13;
sCode += "PROCEDURE Delete" + sTableName + "(ID is string) : boolean" + CR&#13;
sCode += "	RESULT m_cClient.Delete(ID)" + CR&#13;
sCode += "END" + CR&#13;
&#13;
RESULT sCode&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GenerateSwaggerDoc: Gera documentação Swagger/OpenAPI&#13;
//——————————————————————————&#13;
PROCEDURE STATIC GenerateSwaggerDoc(sTableName is string, stFields is array of stFieldInfo) : string&#13;
sSwagger is string&#13;
i is int&#13;
&#13;
```&#13;
sSwagger = "{" + CR&#13;
sSwagger += "  ""openapi"": ""3.0.0""," + CR&#13;
sSwagger += "  ""info"": {" + CR&#13;
sSwagger += "    ""title"": """ + sTableName + " API""," + CR&#13;
sSwagger += "    ""version"": ""1.0.0""," + CR&#13;
sSwagger += "    ""description"": ""API REST para tabela " + sTableName + """" + CR&#13;
sSwagger += "  }," + CR&#13;
sSwagger += "  ""paths"": {" + CR&#13;
&#13;
// GET All&#13;
sSwagger += "    ""/" + Lower(sTableName) + """: {" + CR&#13;
sSwagger += "      ""get"": {" + CR&#13;
sSwagger += "        ""summary"": ""Listar todos os registros""," + CR&#13;
sSwagger += "        ""parameters"": [" + CR&#13;
sSwagger += "          {""name"": ""filter"", ""in"": ""query"", ""schema"": {""type"": ""string""}}," + CR&#13;
sSwagger += "          {""name"": ""limit"", ""in"": ""query"", ""schema"": {""type"": ""integer""}}," + CR&#13;
sSwagger += "          {""name"": ""offset"", ""in"": ""query"", ""schema"": {""type"": ""integer""}}" + CR&#13;
sSwagger += "        ]," + CR&#13;
sSwagger += "        ""responses"": {" + CR&#13;
sSwagger += "          ""200"": {""description"": ""Lista de registros""}" + CR&#13;
sSwagger += "        }" + CR&#13;
sSwagger += "      }," + CR&#13;
&#13;
// POST Create&#13;
sSwagger += "      ""post"": {" + CR&#13;
sSwagger += "        ""summary"": ""Criar novo registro""," + CR&#13;
sSwagger += "        ""requestBody"": {" + CR&#13;
sSwagger += "          ""content"": {" + CR&#13;
sSwagger += "            ""application/json"": {" + CR&#13;
sSwagger += "              ""schema"": " + GenerateJSONSchema(stFields) + CR&#13;
sSwagger += "            }" + CR&#13;
sSwagger += "          }" + CR&#13;
sSwagger += "        }," + CR&#13;
sSwagger += "        ""responses"": {" + CR&#13;
sSwagger += "          ""201"": {""description"": ""Registro criado""}" + CR&#13;
sSwagger += "        }" + CR&#13;
sSwagger += "      }" + CR&#13;
sSwagger += "    }" + CR&#13;
&#13;
sSwagger += "  }" + CR&#13;
sSwagger += "}" + CR&#13;
&#13;
RESULT sSwagger&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GenerateJSONSchema: Gera schema JSON para documentação&#13;
//——————————————————————————&#13;
PROCEDURE STATIC GenerateJSONSchema(stFields is array of stFieldInfo) : string&#13;
sSchema is string&#13;
i is int&#13;
&#13;
```&#13;
sSchema = "{" + CR + "  ""type"": ""object""," + CR + "  ""properties"": {" + CR&#13;
&#13;
FOR i = 1 TO ArrayCount(stFields)&#13;
	IF i &gt; 1 THEN&#13;
		sSchema += "," + CR&#13;
	END&#13;
	&#13;
	sSchema += "    """ + stFields[i].sName + """: {" + CR&#13;
	sSchema += "      ""type"": """ + GetJSONType(stFields[i].nType) + """" + CR&#13;
	&#13;
	IF stFields[i].bMandatory THEN&#13;
		sSchema += "," + CR + "      ""required"": true" + CR&#13;
	END&#13;
	&#13;
	sSchema += "    }"&#13;
END&#13;
&#13;
sSchema += CR + "  }" + CR + "}"&#13;
&#13;
RESULT sSchema&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetJSONType: Converte tipo HFSQL para tipo JSON&#13;
//——————————————————————————&#13;
PROCEDURE STATIC GetJSONType(nHFSQLType is int) : string&#13;
SWITCH nHFSQLType&#13;
CASE hItemText, hItemMemo, hItemBinaryMemo&#13;
RESULT “string”&#13;
CASE hItemNumeric, hItemReal, hItemCurrency&#13;
RESULT “number”&#13;
CASE hItemBoolean&#13;
RESULT “boolean”&#13;
CASE hItemDate, hItemTime, hItemDateTime&#13;
RESULT “string”&#13;
DEFAULT&#13;
RESULT “string”&#13;
END&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ValidateJSON: Valida estrutura JSON contra schema da tabela&#13;
//——————————————————————————&#13;
PROCEDURE STATIC ValidateJSON(sJSON is string, stFields is array of stFieldInfo) : boolean&#13;
varData is Variant&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
	JSONToVariant(varData, sJSON)&#13;
	&#13;
	// Valida cada campo obrigatório&#13;
	FOR i = 1 TO ArrayCount(stFields)&#13;
		IF stFields[i].bMandatory THEN&#13;
			IF NOT VariantExists(varData, stFields[i].sName) THEN&#13;
				RESULT False&#13;
			END&#13;
		END&#13;
	END&#13;
	&#13;
	RESULT True&#13;
	&#13;
EXCEPTION&#13;
	RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO COMPLETO DE USO DO SISTEMA&#13;
//==============================================================================&#13;
&#13;
/*&#13;
//——————————————————————————&#13;
// EXEMPLO 1: Uso básico - Uma tabela&#13;
//——————————————————————————&#13;
&#13;
// Servidor&#13;
serverCliente is CRestCRUDServer(“Cliente”)&#13;
serverCliente.StartServer(8080)&#13;
&#13;
// Cliente&#13;
clienteAPI is CRestCRUDClient(“http://localhost:8080/api/v1”, “cliente”)&#13;
&#13;
// Operações&#13;
novoCliente is Variant&#13;
novoCliente.nome = “Maria Silva”&#13;
novoCliente.email = “maria@teste.com”&#13;
resultado is Variant = clienteAPI.Create(novoCliente)&#13;
&#13;
//——————————————————————————&#13;
// EXEMPLO 2: Uso avançado - Múltiplas tabelas&#13;
//——————————————————————————&#13;
&#13;
// Gerenciador para todas as tabelas&#13;
manager is CRestCRUDManager()&#13;
&#13;
// Inicia servidores para todas as tabelas descobertas&#13;
manager.StartAllServers()&#13;
&#13;
// Gera documentação&#13;
sDocumentacao is string = manager.GetAPIDocumentation()&#13;
fSaveText(“c:\temp\api_docs.md”, sDocumentacao)&#13;
&#13;
//——————————————————————————&#13;
// EXEMPLO 3: Geração de código cliente&#13;
//——————————————————————————&#13;
&#13;
// Gera código cliente específico para tabela Cliente&#13;
sCodigoCliente is string = CRestCRUDHelper.GenerateClientCode(“Cliente”, “http://localhost:8080/api/v1”)&#13;
fSaveText(“c:\temp\CClienteAPI.wl”, sCodigoCliente)&#13;
&#13;
// Gera documentação Swagger&#13;
stCampos is array of stFieldInfo&#13;
// … preenche campos …&#13;
sSwagger is string = CRestCRUDHelper.GenerateSwaggerDoc(“Cliente”, stCampos)&#13;
fSaveText(“c:\temp\swagger.json”, sSwagger)&#13;
&#13;
//——————————————————————————&#13;
// EXEMPLO 4: API com Query SQL personalizada&#13;
//——————————————————————————&#13;
&#13;
sQueryPersonalizada is string = “SELECT c.nome, c.email, p.descricao as perfil “ + …&#13;
“FROM Cliente c INNER JOIN Perfil p ON c.id_perfil = p.id “ + …&#13;
“WHERE c.ativo = 1”&#13;
&#13;
serverQuery is CRestCRUDServer(sQueryPersonalizada)&#13;
serverQuery.StartServer(8081, “/api/v1”)&#13;
&#13;
//——————————————————————————&#13;
// EXEMPLO 5: Cliente com autenticação&#13;
//——————————————————————————&#13;
&#13;
clienteSeguro is CRestCRUDClient(“https://api.empresa.com/v1”, “cliente”, “seu_token_jwt_aqui”)&#13;
clienteSeguro.SetTimeout(60000) // 60 segundos&#13;
&#13;
dados is Variant = clienteSeguro.GetAll(“ativo=1”, 50, 0)&#13;
*/&#13;
&#13;
[/code]&#13;
&#13;
Principais vantagens do sistema completo:&#13;
✅ Zero configuração&#13;
	•	Apenas informe o nome da tabela e o sistema gera automaticamente toda a API REST&#13;
	•	Análise automática da estrutura usando comandos H (HListFile, HListItem, HItemType, etc.)&#13;
✅ Uso extensivo de indirection&#13;
	•	{m_sTableName, indFile} para acesso dinâmico às tabelas&#13;
	•	{m_sTableName + "." + sFieldName, indFile} para campos&#13;
	•	Máximo reaproveitamento de código - uma classe serve para qualquer tabela&#13;
✅ Gerenciamento inteligente&#13;
	•	CRestCRUDManager: Descobre automaticamente todas as tabelas do projeto&#13;
	•	Inicia múltiplos servidores simultaneamente&#13;
	•	Gera documentação automática completa&#13;
✅ Utilitários avançados&#13;
	•	CRestCRUDHelper: Gera código cliente automaticamente&#13;
	•	Documentação Swagger/OpenAPI automática&#13;
	•	Validação de JSON contra schema da tabela&#13;
✅ Recursos especiais&#13;
	•	Campos obrigatórios: Validação automática antes de inserir&#13;
	•	Chaves primárias/estrangeiras: Detecta e usa automaticamente&#13;
	•	Relacionamentos: Analisa links entre tabelas&#13;
	•	Queries SQL: Suporte a SELECT personalizados&#13;
	•	CORS: Configurado para integração web&#13;
	•	Autenticação: Suporte a Bearer tokens&#13;
✅ Facilidade de uso&#13;
Para criar API de uma tabela:&#13;
&#13;
[code:wl]&#13;
servidor is CRestCRUDServer("MinhaTabela")&#13;
servidor.StartServer(8080)&#13;
[/code]&#13;
&#13;
Para consumir a API:&#13;
[code:wl]&#13;
cliente is CRestCRUDClient("http://localhost:8080/api/v1", "minhatabela")&#13;
dados is Variant = cliente.GetAll()&#13;
[/code]&#13;
&#13;
Para múltiplas tabelas:&#13;
[code:wl]&#13;
manager is CRestCRUDManager()&#13;
manager.StartAllServers()  // Inicia API para TODAS as tabelas&#13;
[/code]&#13;
&#13;
📚 Documentação automática&#13;
O sistema gera automaticamente:&#13;
	•	Documentação Markdown com todos os endpoints&#13;
	•	Especificação Swagger/OpenAPI&#13;
	•	Código cliente específico para cada tabela&#13;
	•	Lista de relacionamentos detectados&#13;
Este sistema permite criar APIs REST profissionais em WLanguage com zero configuração manual, aproveitando ao máximo a indirection e os comandos H para máxima flexibilidade e reaproveitamento de código!​​​​​​​​​​​​​​​​&#13;
&#13;
# Guia Completo de Instalação e Uso - CRUD REST API WLanguage&#13;
&#13;
## 📋 Pré-requisitos&#13;
&#13;
- **WinDev 28** ou superior (funciona também no WebDev/WinDev Mobile)&#13;
- **Análise com pelo menos uma tabela** criada&#13;
- **Projeto configurado** com conexão à base de dados&#13;
- **Porta disponível** para o servidor (padrão: 8080)&#13;
&#13;
## 📦 Instalação&#13;
&#13;
### Passo 1: Adicionar as Classes ao Projeto&#13;
&#13;
1. **Crie um novo elemento** no seu projeto: `Ctrl + N`&#13;
1. **Selecione “Classe”**&#13;
1. **Nome**: `CRestCRUDServer`&#13;
1. **Cole o código da classe CRestCRUDServer** do artifact anterior&#13;
1. **Repita para**: `CRestCRUDClient`, `CRestCRUDManager`, `CRestCRUDHelper`&#13;
&#13;
### Passo 2: Verificar Dependências&#13;
&#13;
Certifique-se que seu projeto tem:&#13;
&#13;
- **Componente Web** ativado (para WebServer)&#13;
- **Componente HTTP** ativado (para HTTPSend)&#13;
- **Acesso à análise** configurado&#13;
&#13;
## 🚀 Exemplos Práticos de Uso&#13;
&#13;
### Exemplo 1: API Simples para Uma Tabela&#13;
&#13;
Vamos criar uma API para a tabela `CLIENTE`:&#13;
&#13;
```wlanguage&#13;
//==============================================================================&#13;
// ARQUIVO: ServerCliente.wl&#13;
// Descrição: Exemplo básico - Servidor para tabela Cliente&#13;
//==============================================================================&#13;
&#13;
PROCEDURE Main()&#13;
    // Cria e inicia servidor para tabela Cliente&#13;
    serverCliente is CRestCRUDServer("Cliente")&#13;
    &#13;
    // Inicia servidor na porta 8080&#13;
    IF serverCliente.StartServer(8080, "/api/v1") THEN&#13;
        Info("✅ Servidor Cliente iniciado com sucesso!" + CR + CR + ...&#13;
             "Endpoints disponíveis:" + CR + ...&#13;
             "• GET http://localhost:8080/api/v1/cliente" + CR + ...&#13;
             "• POST http://localhost:8080/api/v1/cliente" + CR + ...&#13;
             "• PUT http://localhost:8080/api/v1/cliente/{id}" + CR + ...&#13;
             "• DELETE http://localhost:8080/api/v1/cliente/{id}")&#13;
             &#13;
        // Mantém servidor ativo&#13;
        LOOP&#13;
            Multitask(100)&#13;
        END&#13;
    ELSE&#13;
        Error("❌ Falha ao iniciar servidor!")&#13;
    END&#13;
END&#13;
```&#13;
&#13;
### Exemplo 2: Cliente Consumindo a API&#13;
&#13;
```wlanguage&#13;
//==============================================================================&#13;
// ARQUIVO: ClienteAPI.wl  &#13;
// Descrição: Exemplo de cliente consumindo API Cliente&#13;
//==============================================================================&#13;
&#13;
PROCEDURE TestarAPICliente()&#13;
    // Cria cliente da API&#13;
    clienteAPI is CRestCRUDClient("http://localhost:8080/api/v1", "cliente")&#13;
    &#13;
    // Define timeout de 30 segundos&#13;
    clienteAPI.SetTimeout(30000)&#13;
    &#13;
    TRY&#13;
        //----------------------------------------------------------------------&#13;
        // 1. CRIAR NOVO CLIENTE&#13;
        //----------------------------------------------------------------------&#13;
        Info("🔄 Criando novo cliente...")&#13;
        &#13;
        novoCliente is Variant&#13;
        novoCliente.nome = "João Silva"&#13;
        novoCliente.email = "joao.silva@email.com"&#13;
        novoCliente.telefone = "11999887766"&#13;
        novoCliente.cidade = "São Paulo"&#13;
        novoCliente.ativo = True&#13;
        &#13;
        clienteCriado is Variant = clienteAPI.Create(novoCliente)&#13;
        &#13;
        sIDCriado is string = clienteCriado.id&#13;
        Info("✅ Cliente criado com sucesso!" + CR + ...&#13;
             "ID: " + sIDCriado + CR + ...&#13;
             "Nome: " + clienteCriado.nome)&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // 2. BUSCAR CLIENTE POR ID&#13;
        //----------------------------------------------------------------------&#13;
        Info("🔄 Buscando cliente por ID...")&#13;
        &#13;
        clienteEncontrado is Variant = clienteAPI.GetByID(sIDCriado)&#13;
        Info("✅ Cliente encontrado:" + CR + ...&#13;
             "Nome: " + clienteEncontrado.nome + CR + ...&#13;
             "Email: " + clienteEncontrado.email + CR + ...&#13;
             "Telefone: " + clienteEncontrado.telefone)&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // 3. ATUALIZAR CLIENTE&#13;
        //----------------------------------------------------------------------&#13;
        Info("🔄 Atualizando cliente...")&#13;
        &#13;
        dadosAtualizacao is Variant&#13;
        dadosAtualizacao.telefone = "11888777666"&#13;
        dadosAtualizacao.observacoes = "Cliente VIP - Atualizado via API"&#13;
        &#13;
        clienteAtualizado is Variant = clienteAPI.Update(sIDCriado, dadosAtualizacao)&#13;
        Info("✅ Cliente atualizado!" + CR + ...&#13;
             "Novo telefone: " + clienteAtualizado.telefone + CR + ...&#13;
             "Observações: " + clienteAtualizado.observacoes)&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // 4. LISTAR TODOS OS CLIENTES&#13;
        //----------------------------------------------------------------------&#13;
        Info("🔄 Listando todos os clientes...")&#13;
        &#13;
        todosClientes is Variant = clienteAPI.GetAll("", 10, 0)&#13;
        &#13;
        sListaClientes is string = "📋 Lista de Clientes:" + CR + CR&#13;
        FOR i = 1 TO ArrayCount(todosClientes)&#13;
            sListaClientes += StringBuild("• ID: %1 - Nome: %2 - Email: %3" + CR, &#13;
                                        todosClientes[i].id,&#13;
                                        todosClientes[i].nome, &#13;
                                        todosClientes[i].email)&#13;
        END&#13;
        &#13;
        Info(sListaClientes)&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // 5. BUSCAR COM FILTRO&#13;
        //----------------------------------------------------------------------&#13;
        Info("🔄 Buscando clientes ativos...")&#13;
        &#13;
        clientesAtivos is Variant = clienteAPI.GetAll("ativo=1", 20, 0)&#13;
        Info("✅ Encontrados " + ArrayCount(clientesAtivos) + " clientes ativos")&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // 6. EXCLUIR CLIENTE (OPCIONAL)&#13;
        //----------------------------------------------------------------------&#13;
        nResposta is int = YesNo("❓ Deseja excluir o cliente criado no teste?")&#13;
        &#13;
        IF nResposta = Yes THEN&#13;
            IF clienteAPI.Delete(sIDCriado) THEN&#13;
                Info("✅ Cliente excluído com sucesso!")&#13;
            ELSE&#13;
                Error("❌ Erro ao excluir cliente")&#13;
            END&#13;
        END&#13;
        &#13;
    EXCEPTION&#13;
        Error("❌ Erro durante teste da API: " + CR + ExceptionInfo())&#13;
    END&#13;
END&#13;
```&#13;
&#13;
### Exemplo 3: Servidor Multi-Tabelas Automático&#13;
&#13;
```wlanguage&#13;
//==============================================================================&#13;
// ARQUIVO: ServidorCompleto.wl&#13;
// Descrição: Servidor automático para TODAS as tabelas do projeto&#13;
//==============================================================================&#13;
&#13;
PROCEDURE IniciarServidorCompleto()&#13;
    // Cria gerenciador para todas as tabelas&#13;
    manager is CRestCRUDManager()&#13;
    &#13;
    Info("🔄 Iniciando descoberta automática de tabelas...")&#13;
    &#13;
    // Gera e exibe documentação antes de iniciar&#13;
    sDocumentacao is string = manager.GetAPIDocumentation()&#13;
    &#13;
    // Salva documentação em arquivo&#13;
    IF fSaveText(fExeDir() + "\API_Documentation.md", sDocumentacao) THEN&#13;
        Info("📄 Documentação salva em: " + fExeDir() + "\API_Documentation.md")&#13;
    END&#13;
    &#13;
    // Exibe prévia da documentação&#13;
    Info("📋 Tabelas descobertas:" + CR + CR + Left(sDocumentacao, 500) + "...")&#13;
    &#13;
    // Pergunta se deseja continuar&#13;
    IF YesNo("🚀 Iniciar servidores para todas as tabelas?") = Yes THEN&#13;
        &#13;
        Info("🔄 Iniciando servidores...")&#13;
        &#13;
        IF manager.StartAllServers() THEN&#13;
            Info("✅ Todos os servidores iniciados com sucesso!" + CR + CR + ...&#13;
                 "📖 Consulte o arquivo API_Documentation.md para detalhes completos" + CR + CR + ...&#13;
                 "🌐 Exemplo de acesso:" + CR + ...&#13;
                 "http://localhost:8080/api/v1/cliente" + CR + ...&#13;
                 "http://localhost:8081/api/v1/produto" + CR + ...&#13;
                 "http://localhost:8082/api/v1/pedido")&#13;
                 &#13;
            // Mantém servidores ativos&#13;
            Info("⚡ Servidores rodando... Pressione OK para parar.")&#13;
            &#13;
        ELSE&#13;
            Error("❌ Falha ao iniciar alguns servidores!")&#13;
        END&#13;
    END&#13;
END&#13;
```&#13;
&#13;
### Exemplo 4: Cliente com Tratamento de Erros Avançado&#13;
&#13;
```wlanguage&#13;
//==============================================================================&#13;
// ARQUIVO: ClienteAvancado.wl&#13;
// Descrição: Cliente com tratamento completo de erros e logging&#13;
//==============================================================================&#13;
&#13;
PROCEDURE TestarAPIAvancada()&#13;
    sLog is string = ""&#13;
    &#13;
    // Função interna para logging&#13;
    INTERNAL PROCEDURE Log(sMessage)&#13;
        sTimeStamp is string = DateTimeToString(Now(), "DD/MM/YYYY HH:MM:SS")&#13;
        sLog += "[" + sTimeStamp + "] " + sMessage + CR&#13;
        Trace(sMessage)&#13;
    END&#13;
    &#13;
    Log("🚀 Iniciando teste avançado da API")&#13;
    &#13;
    // Cria cliente com configurações personalizadas&#13;
    produtoAPI is CRestCRUDClient("http://localhost:8081/api/v1", "produto")&#13;
    produtoAPI.SetTimeout(60000) // 60 segundos&#13;
    &#13;
    TRY&#13;
        //----------------------------------------------------------------------&#13;
        // Teste de conectividade&#13;
        //----------------------------------------------------------------------&#13;
        Log("🔗 Testando conectividade...")&#13;
        &#13;
        testeProdutos is Variant = produtoAPI.GetAll("", 1, 0)&#13;
        Log("✅ Conectividade OK - API respondendo")&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // Operações em lote&#13;
        //----------------------------------------------------------------------&#13;
        Log("📦 Criando produtos em lote...")&#13;
        &#13;
        arrayProdutos is array of Variant&#13;
        &#13;
        FOR i = 1 TO 5&#13;
            novoProduto is Variant&#13;
            novoProduto.nome = "Produto Teste " + i&#13;
            novoProduto.preco = 10.50 * i&#13;
            novoProduto.categoria = "Eletrônicos"&#13;
            novoProduto.estoque = 100 + i * 10&#13;
            novoProduto.ativo = True&#13;
            &#13;
            ArrayAdd(arrayProdutos, novoProduto)&#13;
        END&#13;
        &#13;
        arrayIDsCriados is array of string&#13;
        &#13;
        FOR i = 1 TO ArrayCount(arrayProdutos)&#13;
            produtoCriado is Variant = produtoAPI.Create(arrayProdutos[i])&#13;
            ArrayAdd(arrayIDsCriados, produtoCriado.id)&#13;
            Log("✅ Produto criado - ID: " + produtoCriado.id + " - Nome: " + produtoCriado.nome)&#13;
        END&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // Teste de paginação&#13;
        //----------------------------------------------------------------------&#13;
        Log("📄 Testando paginação...")&#13;
        &#13;
        nPagina is int = 1&#13;
        nTamanhoPagina is int = 3&#13;
        nTotalRegistros is int = 0&#13;
        &#13;
        LOOP&#13;
            produtosPagina is Variant = produtoAPI.GetAll("", nTamanhoPagina, (nPagina - 1) * nTamanhoPagina)&#13;
            &#13;
            nRegistrosPagina is int = ArrayCount(produtosPagina)&#13;
            &#13;
            IF nRegistrosPagina = 0 THEN&#13;
                BREAK&#13;
            END&#13;
            &#13;
            nTotalRegistros += nRegistrosPagina&#13;
            Log(StringBuild("📄 Página %1: %2 registros", nPagina, nRegistrosPagina))&#13;
            &#13;
            nPagina++&#13;
            &#13;
            // Evita loop infinito&#13;
            IF nPagina &gt; 10 THEN&#13;
                BREAK&#13;
            END&#13;
        END&#13;
        &#13;
        Log("📊 Total de registros encontrados: " + nTotalRegistros)&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // Atualização em lote&#13;
        //----------------------------------------------------------------------&#13;
        Log("🔄 Atualizando produtos criados...")&#13;
        &#13;
        FOR i = 1 TO ArrayCount(arrayIDsCriados)&#13;
            dadosAtualizacao is Variant&#13;
            dadosAtualizacao.preco = dadosAtualizacao.preco * 1.1 // Aumento de 10%&#13;
            dadosAtualizacao.observacoes = "Preço atualizado automaticamente"&#13;
            &#13;
            produtoAtualizado is Variant = produtoAPI.Update(arrayIDsCriados[i], dadosAtualizacao)&#13;
            Log("💰 Preço atualizado - ID: " + arrayIDsCriados[i] + " - Novo preço: " + produtoAtualizado.preco)&#13;
        END&#13;
        &#13;
        //----------------------------------------------------------------------&#13;
        // Limpeza (exclusão dos produtos de teste)&#13;
        //----------------------------------------------------------------------&#13;
        IF YesNo("🗑️ Excluir produtos de teste criados?") = Yes THEN&#13;
            Log("🗑️ Excluindo produtos de teste...")&#13;
            &#13;
            FOR i = 1 TO ArrayCount(arrayIDsCriados)&#13;
                IF produtoAPI.Delete(arrayIDsCriados[i]) THEN&#13;
                    Log("✅ Produto excluído - ID: " + arrayIDsCriados[i])&#13;
                ELSE&#13;
                    Log("❌ Erro ao excluir produto - ID: " + arrayIDsCriados[i])&#13;
                END&#13;
            END&#13;
        END&#13;
        &#13;
    EXCEPTION&#13;
        Log("❌ ERRO: " + ExceptionInfo())&#13;
    END&#13;
    &#13;
    // Salva log completo&#13;
    sLogFile is string = fExeDir() + "\API_Test_" + DateToString(Today(), "YYYYMMDD_HHMMSS") + ".log"&#13;
    &#13;
    IF fSaveText(sLogFile, sLog) THEN&#13;
        Log("📄 Log salvo em: " + sLogFile)&#13;
        Info("✅ Teste concluído!" + CR + CR + "📄 Log detalhado salvo em:" + CR + sLogFile)&#13;
    END&#13;
END&#13;
```&#13;
&#13;
## 🔧 Configurações Avançadas&#13;
&#13;
### Personalizar Portas e Caminhos&#13;
&#13;
```wlanguage&#13;
// Servidor em porta específica&#13;
servidor is CRestCRUDServer("MinhaTabela")&#13;
servidor.StartServer(9090, "/minha-api/v2")&#13;
&#13;
// Cliente para API personalizada  &#13;
cliente is CRestCRUDClient("http://localhost:9090/minha-api/v2", "minhatabela")&#13;
```&#13;
&#13;
### API com Autenticação&#13;
&#13;
```wlanguage&#13;
// Cliente com token JWT&#13;
clienteSeguro is CRestCRUDClient("https://api.empresa.com/v1", "cliente", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...")&#13;
&#13;
// Operações autenticadas&#13;
dados is Variant = clienteSeguro.GetAll()&#13;
```&#13;
&#13;
### Servidor para Query SQL Personalizada&#13;
&#13;
```wlanguage&#13;
sQuery is string = "SELECT c.nome, c.email, p.descricao as perfil " + ...&#13;
                   "FROM Cliente c INNER JOIN Perfil p ON c.id_perfil = p.id " + ...&#13;
                   "WHERE c.ativo = 1"&#13;
&#13;
serverQuery is CRestCRUDServer(sQuery)&#13;
serverQuery.StartServer(8085, "/api/v1")&#13;
```&#13;
&#13;
## 🐛 Solução de Problemas&#13;
&#13;
### Erro: “Tabela não encontrada”&#13;
&#13;
```wlanguage&#13;
// Verifique se a tabela existe na análise&#13;
sTables is string = HListFile()&#13;
IF Position(sTables, "MinhaTabela", firstRank, IgnoreCase) = 0 THEN&#13;
    Error("Tabela não encontrada na análise!")&#13;
END&#13;
```&#13;
&#13;
### Erro: “Porta já em uso”&#13;
&#13;
```wlanguage&#13;
// Teste portas diferentes&#13;
FOR nPorta = 8080 TO 8090&#13;
    IF servidor.StartServer(nPorta) THEN&#13;
        Info("Servidor iniciado na porta: " + nPorta)&#13;
        BREAK&#13;
    END&#13;
END&#13;
```&#13;
&#13;
### Erro: “Conexão recusada”&#13;
&#13;
```wlanguage&#13;
// Verifique se o servidor está rodando&#13;
cHTTP is httpRequest&#13;
cHTTP.URL = "http://localhost:8080/api/v1/cliente"&#13;
cHTTP.Method = "GET"&#13;
&#13;
cResponse is httpResponse = HTTPSend(cHTTP)&#13;
IF cResponse.StatusCode &lt;&gt; 200 THEN&#13;
    Error("Servidor não está respondendo!")&#13;
END&#13;
```&#13;
&#13;
## 📚 Próximos Passos&#13;
&#13;
1. **Teste os exemplos** acima em seu projeto&#13;
1. **Adapte as classes** para suas necessidades específicas&#13;
1. **Explore a documentação automática** gerada&#13;
1. **Implemente autenticação** se necessário&#13;
1. **Configure HTTPS** para produção&#13;
1. **Adicione logs** e monitoramento&#13;
&#13;
## 🎯 Dicas de Performance&#13;
&#13;
- Use **filtros** para limitar resultados: `GetAll("ativo=1", 50, 0)`&#13;
- Configure **timeout** adequado: `SetTimeout(30000)`&#13;
- Implemente **cache** no lado cliente se necessário&#13;
- Use **paginação** para grandes volumes de dados&#13;
- Monitore **uso de memória** em servidores com muitas tabelas&#13;
&#13;
Criei um guia completo e prático com exemplos reais de instalação e uso do sistema CRUD REST API em WLanguage!&#13;
🎯 Destaques do Guia:&#13;
📦 Instalação Simples&#13;
	•	Passo a passo para adicionar as classes ao projeto&#13;
	•	Verificação de dependências necessárias&#13;
	•	Configuração mínima requerida&#13;
🚀 Exemplos Práticos Escaláveis&#13;
	1.	API Simples - Servidor básico para uma tabela&#13;
	2.	Cliente Completo - Todos os métodos CRUD com tratamento de erros&#13;
	3.	Multi-Tabelas - Servidor automático para todas as tabelas&#13;
	4.	Cliente Avançado - Operações em lote, paginação, logging&#13;
🔧 Configurações Flexíveis&#13;
	•	Portas personalizadas&#13;
	•	Caminhos de API customizados&#13;
	•	Autenticação JWT&#13;
	•	Queries SQL personalizadas&#13;
🐛 Solução de Problemas&#13;
	•	Erros comuns e suas soluções&#13;
	•	Verificações de conectividade&#13;
	•	Testes de porta e configuração&#13;
💡 Casos de Uso Reais:&#13;
✅ Para desenvolvimento rápido:&#13;
[code:wl]&#13;
servidor is CRestCRUDServer("Cliente")&#13;
servidor.StartServer(8080)&#13;
[/code]&#13;
&#13;
&#13;
✅ Para produção empresarial:&#13;
[code:wl]&#13;
manager is CRestCRUDManager()&#13;
manager.StartAllServers() // API para TODAS as tabelas!&#13;
[/code]&#13;
 &#13;
&#13;
✅ Para integração externa:&#13;
[code:wl]&#13;
clienteAPI is CRestCRUDClient("https://api.empresa.com/v1", "cliente", "token_jwt")&#13;
dados is Variant = clienteAPI.GetAll()&#13;
[/code]&#13;
&#13;
&#13;
O sistema permite desde protótipos rápidos até soluções enterprise completas, aproveitando ao máximo a análise do WinDev e indirection para máximo reaproveitamento de código!&#13;
&#13;
Exemplo de pedido e itens&#13;
[code:wl]&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO COMPLETO: SISTEMA DE PEDIDOS E ITENS&#13;
// Descrição: Demonstra uso prático com relacionamentos mestre-detalhe&#13;
// Tabelas: PEDIDO, ITEM_PEDIDO, CLIENTE, PRODUTO&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
//==============================================================================&#13;
// ESTRUTURA DAS TABELAS (Para referência)&#13;
//==============================================================================&#13;
/*&#13;
CLIENTE:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- nome (Text 100, Mandatory)&#13;
- email (Text 150, Mandatory, Unique)&#13;
- telefone (Text 20)&#13;
- endereco (Text 200)&#13;
- cidade (Text 50)&#13;
- ativo (Boolean, Default True)&#13;
&#13;
PRODUTO:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- nome (Text 100, Mandatory)&#13;
- descricao (Text 500)&#13;
- preco (Currency, Mandatory)&#13;
- estoque (Numeric, Default 0)&#13;
- categoria (Text 50)&#13;
- ativo (Boolean, Default True)&#13;
&#13;
PEDIDO:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- id_cliente (Numeric, Foreign Key -&gt; CLIENTE.id, Mandatory)&#13;
- data_pedido (Date, Mandatory, Default Today)&#13;
- hora_pedido (Time, Default Now)&#13;
- status (Text 20, Default “PENDENTE”) // PENDENTE, CONFIRMADO, ENVIADO, ENTREGUE, CANCELADO&#13;
- valor_total (Currency, Default 0)&#13;
- observacoes (Text 500)&#13;
- data_entrega (Date)&#13;
&#13;
ITEM_PEDIDO:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- id_pedido (Numeric, Foreign Key -&gt; PEDIDO.id, Mandatory)&#13;
- id_produto (Numeric, Foreign Key -&gt; PRODUTO.id, Mandatory)&#13;
- quantidade (Numeric, Mandatory, Default 1)&#13;
- preco_unitario (Currency, Mandatory)&#13;
- preco_total (Currency, Calculated: quantidade * preco_unitario)&#13;
- observacoes (Text 200)&#13;
  */&#13;
&#13;
//==============================================================================&#13;
// SERVIDOR: INICIALIZANDO APIS PARA TODAS AS TABELAS&#13;
//==============================================================================&#13;
&#13;
PROCEDURE IniciarServidorPedidos()&#13;
// Gerenciador para todas as tabelas do sistema&#13;
manager is CRestCRUDManager()&#13;
&#13;
```&#13;
Info("🚀 Iniciando Sistema de Pedidos - APIs REST")&#13;
&#13;
// Exibe documentação das tabelas encontradas&#13;
sDoc is string = manager.GetAPIDocumentation()&#13;
Trace("📋 Tabelas do sistema:" + CR + sDoc)&#13;
&#13;
// Inicia servidores para todas as tabelas&#13;
IF manager.StartAllServers() THEN&#13;
    Info("✅ Servidor de Pedidos iniciado com sucesso!" + CR + CR + ...&#13;
         "🌐 APIs disponíveis:" + CR + ...&#13;
         "• CLIENTE: http://localhost:8080/api/v1/cliente" + CR + ...&#13;
         "• PRODUTO: http://localhost:8081/api/v1/produto" + CR + ...&#13;
         "• PEDIDO: http://localhost:8082/api/v1/pedido" + CR + ...&#13;
         "• ITEM_PEDIDO: http://localhost:8083/api/v1/item_pedido" + CR + CR + ...&#13;
         "📖 Consulte API_Documentation.md para detalhes")&#13;
    &#13;
    // Mantém servidores ativos&#13;
    Info("⚡ Servidores rodando... Pressione OK para parar.")&#13;
ELSE&#13;
    Error("❌ Falha ao iniciar servidores!")&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CPedidoManager&#13;
// Descrição: Gerenciador de alto nível para operações de pedidos&#13;
//==============================================================================&#13;
&#13;
CPedidoManager is Class&#13;
PRIVATE&#13;
m_cClienteAPI is CRestCRUDClient&#13;
m_cProdutoAPI is CRestCRUDClient  &#13;
m_cPedidoAPI is CRestCRUDClient&#13;
m_cItemPedidoAPI is CRestCRUDClient&#13;
m_sBaseURL is string&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa todos os clientes API&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sBaseURL is string = “http://localhost”)&#13;
m_sBaseURL = sBaseURL&#13;
&#13;
```&#13;
// Inicializa clientes para cada tabela&#13;
m_cClienteAPI = new CRestCRUDClient(sBaseURL + ":8080/api/v1", "cliente")&#13;
m_cProdutoAPI = new CRestCRUDClient(sBaseURL + ":8081/api/v1", "produto") &#13;
m_cPedidoAPI = new CRestCRUDClient(sBaseURL + ":8082/api/v1", "pedido")&#13;
m_cItemPedidoAPI = new CRestCRUDClient(sBaseURL + ":8083/api/v1", "item_pedido")&#13;
&#13;
// Timeout de 60 segundos para operações complexas&#13;
m_cClienteAPI.SetTimeout(60000)&#13;
m_cProdutoAPI.SetTimeout(60000)&#13;
m_cPedidoAPI.SetTimeout(60000)&#13;
m_cItemPedidoAPI.SetTimeout(60000)&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS DE NEGÓCIO - PEDIDOS COMPLETOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// CriarPedidoCompleto: Cria pedido com itens em uma transação&#13;
//——————————————————————————&#13;
PROCEDURE CriarPedidoCompleto(varPedidoCompleto is Variant) : Variant&#13;
varPedidoCriado is Variant&#13;
varResult is Variant&#13;
arrayItens is array of Variant&#13;
nValorTotal is currency = 0&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    Trace("🛒 Iniciando criação de pedido completo...")&#13;
    &#13;
    // 1. Valida se cliente existe&#13;
    varCliente is Variant = m_cClienteAPI.GetByID(varPedidoCompleto.id_cliente)&#13;
    IF VariantToJSON(varCliente) = "null" THEN&#13;
        ExceptionThrow(1, "Cliente não encontrado: " + varPedidoCompleto.id_cliente)&#13;
    END&#13;
    &#13;
    Trace("✅ Cliente validado: " + varCliente.nome)&#13;
    &#13;
    // 2. Valida produtos e calcula valor total&#13;
    IF VariantExists(varPedidoCompleto, "itens") THEN&#13;
        arrayItens = varPedidoCompleto.itens&#13;
        &#13;
        FOR i = 1 TO ArrayCount(arrayItens)&#13;
            // Valida produto&#13;
            varProduto is Variant = m_cProdutoAPI.GetByID(arrayItens[i].id_produto)&#13;
            IF VariantToJSON(varProduto) = "null" THEN&#13;
                ExceptionThrow(2, "Produto não encontrado: " + arrayItens[i].id_produto)&#13;
            END&#13;
            &#13;
            // Valida estoque&#13;
            IF varProduto.estoque &lt; arrayItens[i].quantidade THEN&#13;
                ExceptionThrow(3, "Estoque insuficiente para produto: " + varProduto.nome + &#13;
                                " (Disponível: " + varProduto.estoque + &#13;
                                ", Solicitado: " + arrayItens[i].quantidade + ")")&#13;
            END&#13;
            &#13;
            // Usa preço atual do produto se não informado&#13;
            IF NOT VariantExists(arrayItens[i], "preco_unitario") THEN&#13;
                arrayItens[i].preco_unitario = varProduto.preco&#13;
            END&#13;
            &#13;
            // Calcula preço total do item&#13;
            arrayItens[i].preco_total = arrayItens[i].quantidade * arrayItens[i].preco_unitario&#13;
            nValorTotal += arrayItens[i].preco_total&#13;
            &#13;
            Trace("✅ Item validado: " + varProduto.nome + &#13;
                  " (Qtd: " + arrayItens[i].quantidade + &#13;
                  ", Valor: " + arrayItens[i].preco_total + ")")&#13;
        END&#13;
    END&#13;
    &#13;
    // 3. Cria o pedido (cabeçalho)&#13;
    varNovoPedido is Variant&#13;
    varNovoPedido.id_cliente = varPedidoCompleto.id_cliente&#13;
    varNovoPedido.data_pedido = IIF(VariantExists(varPedidoCompleto, "data_pedido"), &#13;
                                   varPedidoCompleto.data_pedido, &#13;
                                   DateToString(Today(), "YYYY-MM-DD"))&#13;
    varNovoPedido.hora_pedido = IIF(VariantExists(varPedidoCompleto, "hora_pedido"), &#13;
                                   varPedidoCompleto.hora_pedido, &#13;
                                   TimeToString(Now(), "HH:MM:SS"))&#13;
    varNovoPedido.status = IIF(VariantExists(varPedidoCompleto, "status"), &#13;
                              varPedidoCompleto.status, &#13;
                              "PENDENTE")&#13;
    varNovoPedido.valor_total = nValorTotal&#13;
    varNovoPedido.observacoes = IIF(VariantExists(varPedidoCompleto, "observacoes"), &#13;
                                   varPedidoCompleto.observacoes, &#13;
                                   "")&#13;
    &#13;
    varPedidoCriado = m_cPedidoAPI.Create(varNovoPedido)&#13;
    &#13;
    Trace("✅ Pedido criado - ID: " + varPedidoCriado.id + ", Valor: " + varPedidoCriado.valor_total)&#13;
    &#13;
    // 4. Cria os itens do pedido&#13;
    arrayItensCriados is array of Variant&#13;
    &#13;
    FOR i = 1 TO ArrayCount(arrayItens)&#13;
        varNovoItem is Variant&#13;
        varNovoItem.id_pedido = varPedidoCriado.id&#13;
        varNovoItem.id_produto = arrayItens[i].id_produto&#13;
        varNovoItem.quantidade = arrayItens[i].quantidade&#13;
        varNovoItem.preco_unitario = arrayItens[i].preco_unitario&#13;
        varNovoItem.preco_total = arrayItens[i].preco_total&#13;
        varNovoItem.observacoes = IIF(VariantExists(arrayItens[i], "observacoes"), &#13;
                                     arrayItens[i].observacoes, &#13;
                                     "")&#13;
        &#13;
        varItemCriado is Variant = m_cItemPedidoAPI.Create(varNovoItem)&#13;
        ArrayAdd(arrayItensCriados, varItemCriado)&#13;
        &#13;
        // Atualiza estoque do produto&#13;
        AtualizarEstoque(arrayItens[i].id_produto, -arrayItens[i].quantidade)&#13;
        &#13;
        Trace("✅ Item criado - ID: " + varItemCriado.id + &#13;
              ", Produto: " + arrayItens[i].id_produto + &#13;
              ", Quantidade: " + arrayItens[i].quantidade)&#13;
    END&#13;
    &#13;
    // 5. Monta resultado completo&#13;
    varResult.pedido = varPedidoCriado&#13;
    varResult.itens = arrayItensCriados&#13;
    varResult.cliente = varCliente&#13;
    varResult.resumo.total_itens = ArrayCount(arrayItensCriados)&#13;
    varResult.resumo.valor_total = nValorTotal&#13;
    &#13;
    Trace("🎉 Pedido completo criado com sucesso!")&#13;
    &#13;
    RESULT varResult&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao criar pedido: " + ExceptionInfo())&#13;
    &#13;
    // Se houve erro e pedido foi criado, tenta cancelar&#13;
    IF VariantExists(varPedidoCriado, "id") THEN&#13;
        CancelarPedido(varPedidoCriado.id, "Erro durante criação")&#13;
    END&#13;
    &#13;
    RESULT Null&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ObterPedidoCompleto: Busca pedido com todos os dados relacionados&#13;
//——————————————————————————&#13;
PROCEDURE ObterPedidoCompleto(sIDPedido is string) : Variant&#13;
varResult is Variant&#13;
varPedido is Variant&#13;
varCliente is Variant&#13;
arrayItens is array of Variant&#13;
arrayItensDetalhados is array of Variant&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    // 1. Busca dados do pedido&#13;
    varPedido = m_cPedidoAPI.GetByID(sIDPedido)&#13;
    IF VariantToJSON(varPedido) = "null" THEN&#13;
        ExceptionThrow(1, "Pedido não encontrado: " + sIDPedido)&#13;
    END&#13;
    &#13;
    // 2. Busca dados do cliente&#13;
    varCliente = m_cClienteAPI.GetByID(varPedido.id_cliente)&#13;
    &#13;
    // 3. Busca itens do pedido&#13;
    arrayItens = m_cItemPedidoAPI.GetAll("id_pedido=" + sIDPedido)&#13;
    &#13;
    // 4. Enriquece itens com dados dos produtos&#13;
    FOR i = 1 TO ArrayCount(arrayItens)&#13;
        varItemDetalhado is Variant = arrayItens[i]&#13;
        &#13;
        // Busca dados do produto&#13;
        varProduto is Variant = m_cProdutoAPI.GetByID(arrayItens[i].id_produto)&#13;
        &#13;
        // Adiciona informações do produto ao item&#13;
        varItemDetalhado.produto_nome = varProduto.nome&#13;
        varItemDetalhado.produto_descricao = varProduto.descricao&#13;
        varItemDetalhado.produto_categoria = varProduto.categoria&#13;
        varItemDetalhado.produto_estoque_atual = varProduto.estoque&#13;
        &#13;
        ArrayAdd(arrayItensDetalhados, varItemDetalhado)&#13;
    END&#13;
    &#13;
    // 5. Monta resultado completo&#13;
    varResult.pedido = varPedido&#13;
    varResult.cliente = varCliente&#13;
    varResult.itens = arrayItensDetalhados&#13;
    varResult.resumo.total_itens = ArrayCount(arrayItensDetalhados)&#13;
    varResult.resumo.valor_total = varPedido.valor_total&#13;
    &#13;
    RESULT varResult&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao obter pedido completo: " + ExceptionInfo())&#13;
    RESULT Null&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AtualizarStatusPedido: Atualiza status do pedido&#13;
//——————————————————————————&#13;
PROCEDURE AtualizarStatusPedido(sIDPedido is string, sNovoStatus is string, sObservacao is string = “”) : boolean&#13;
varDados is Variant&#13;
&#13;
```&#13;
TRY&#13;
    varDados.status = sNovoStatus&#13;
    &#13;
    IF sObservacao &lt;&gt; "" THEN&#13;
        // Busca observações atuais&#13;
        varPedido is Variant = m_cPedidoAPI.GetByID(sIDPedido)&#13;
        sObservacaoAtual is string = IIF(VariantExists(varPedido, "observacoes"), varPedido.observacoes, "")&#13;
        &#13;
        // Adiciona nova observação com timestamp&#13;
        sTimestamp is string = DateTimeToString(Now(), "DD/MM/YYYY HH:MM:SS")&#13;
        varDados.observacoes = sObservacaoAtual + CR + "[" + sTimestamp + "] Status: " + sNovoStatus + " - " + sObservacao&#13;
    END&#13;
    &#13;
    varAtualizado is Variant = m_cPedidoAPI.Update(sIDPedido, varDados)&#13;
    &#13;
    Trace("✅ Status do pedido " + sIDPedido + " atualizado para: " + sNovoStatus)&#13;
    RESULT True&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao atualizar status: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CancelarPedido: Cancela pedido e reverte estoque&#13;
//——————————————————————————&#13;
PROCEDURE CancelarPedido(sIDPedido is string, sMotivo is string = “”) : boolean&#13;
arrayItens is array of Variant&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    // 1. Busca itens do pedido para reverter estoque&#13;
    arrayItens = m_cItemPedidoAPI.GetAll("id_pedido=" + sIDPedido)&#13;
    &#13;
    // 2. Reverte estoque de cada item&#13;
    FOR i = 1 TO ArrayCount(arrayItens)&#13;
        AtualizarEstoque(arrayItens[i].id_produto, arrayItens[i].quantidade)&#13;
        Trace("↩️ Estoque revertido - Produto: " + arrayItens[i].id_produto + ", Qtd: " + arrayItens[i].quantidade)&#13;
    END&#13;
    &#13;
    // 3. Atualiza status do pedido&#13;
    sObservacaoCancelamento is string = "Pedido cancelado"&#13;
    IF sMotivo &lt;&gt; "" THEN&#13;
        sObservacaoCancelamento += " - Motivo: " + sMotivo&#13;
    END&#13;
    &#13;
    RESULT AtualizarStatusPedido(sIDPedido, "CANCELADO", sObservacaoCancelamento)&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao cancelar pedido: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS AUXILIARES&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// AtualizarEstoque: Atualiza estoque do produto&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AtualizarEstoque(sIDProduto is string, nQuantidade is int) : boolean&#13;
TRY&#13;
// Busca produto atual&#13;
varProduto is Variant = m_cProdutoAPI.GetByID(sIDProduto)&#13;
&#13;
```&#13;
    // Calcula novo estoque&#13;
    nNovoEstoque is int = varProduto.estoque + nQuantidade&#13;
    &#13;
    IF nNovoEstoque &lt; 0 THEN&#13;
        ExceptionThrow(1, "Estoque não pode ficar negativo")&#13;
    END&#13;
    &#13;
    // Atualiza estoque&#13;
    varDados is Variant&#13;
    varDados.estoque = nNovoEstoque&#13;
    &#13;
    m_cProdutoAPI.Update(sIDProduto, varDados)&#13;
    &#13;
    Trace("📦 Estoque atualizado - Produto: " + sIDProduto + &#13;
          ", Anterior: " + varProduto.estoque + &#13;
          ", Novo: " + nNovoEstoque)&#13;
    &#13;
    RESULT True&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao atualizar estoque: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ListarPedidosPorCliente: Lista pedidos de um cliente&#13;
//——————————————————————————&#13;
PROCEDURE ListarPedidosPorCliente(sIDCliente is string, sStatus is string = “”) : Variant&#13;
sFilter is string = “id_cliente=” + sIDCliente&#13;
&#13;
```&#13;
IF sStatus &lt;&gt; "" THEN&#13;
    sFilter += " AND status='" + sStatus + "'"&#13;
END&#13;
&#13;
RESULT m_cPedidoAPI.GetAll(sFilter, 100, 0)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ListarPedidosPorPeriodo: Lista pedidos por período&#13;
//——————————————————————————&#13;
PROCEDURE ListarPedidosPorPeriodo(dDataInicio is Date, dDataFim is Date) : Variant&#13;
sFilter is string = StringBuild(“data_pedido&gt;=’%1’ AND data_pedido&lt;=’%2’”,&#13;
DateToString(dDataInicio, “YYYY-MM-DD”),&#13;
DateToString(dDataFim, “YYYY-MM-DD”))&#13;
&#13;
```&#13;
RESULT m_cPedidoAPI.GetAll(sFilter, 1000, 0)&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE USO PRÁTICO&#13;
//==============================================================================&#13;
&#13;
PROCEDURE TestarSistemaPedidos()&#13;
// Inicializa gerenciador&#13;
pedidoManager is CPedidoManager()&#13;
&#13;
```&#13;
Info("🧪 Iniciando teste do Sistema de Pedidos...")&#13;
&#13;
TRY&#13;
    //----------------------------------------------------------------------&#13;
    // 1. CRIAR DADOS DE TESTE&#13;
    //----------------------------------------------------------------------&#13;
    Info("📝 Criando dados de teste...")&#13;
    &#13;
    // Criar cliente de teste&#13;
    novoCliente is Variant&#13;
    novoCliente.nome = "João Silva"&#13;
    novoCliente.email = "joao.teste@email.com"&#13;
    novoCliente.telefone = "11999887766"&#13;
    novoCliente.endereco = "Rua das Flores, 123"&#13;
    novoCliente.cidade = "São Paulo"&#13;
    &#13;
    clienteCriado is Variant = pedidoManager.m_cClienteAPI.Create(novoCliente)&#13;
    sIDCliente is string = clienteCriado.id&#13;
    &#13;
    // Criar produtos de teste&#13;
    arrayProdutosCriados is array of string&#13;
    &#13;
    FOR i = 1 TO 3&#13;
        novoProduto is Variant&#13;
        novoProduto.nome = "Produto Teste " + i&#13;
        novoProduto.descricao = "Descrição do produto " + i&#13;
        novoProduto.preco = 25.50 * i&#13;
        novoProduto.estoque = 100&#13;
        novoProduto.categoria = "Eletrônicos"&#13;
        &#13;
        produtoCriado is Variant = pedidoManager.m_cProdutoAPI.Create(novoProduto)&#13;
        ArrayAdd(arrayProdutosCriados, produtoCriado.id)&#13;
    END&#13;
    &#13;
    Info("✅ Dados de teste criados - Cliente: " + sIDCliente + ", Produtos: " + ArrayCount(arrayProdutosCriados))&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 2. CRIAR PEDIDO COMPLETO&#13;
    //----------------------------------------------------------------------&#13;
    Info("🛒 Criando pedido completo...")&#13;
    &#13;
    pedidoCompleto is Variant&#13;
    pedidoCompleto.id_cliente = sIDCliente&#13;
    pedidoCompleto.observacoes = "Pedido de teste criado via API"&#13;
    &#13;
    // Itens do pedido&#13;
    arrayItens is array of Variant&#13;
    &#13;
    FOR i = 1 TO ArrayCount(arrayProdutosCriados)&#13;
        novoItem is Variant&#13;
        novoItem.id_produto = arrayProdutosCriados[i]&#13;
        novoItem.quantidade = i * 2  // 2, 4, 6&#13;
        novoItem.observacoes = "Item " + i + " do pedido de teste"&#13;
        &#13;
        ArrayAdd(arrayItens, novoItem)&#13;
    END&#13;
    &#13;
    pedidoCompleto.itens = arrayItens&#13;
    &#13;
    // Cria pedido&#13;
    resultadoPedido is Variant = pedidoManager.CriarPedidoCompleto(pedidoCompleto)&#13;
    sIDPedido is string = resultadoPedido.pedido.id&#13;
    &#13;
    sResumo is string = StringBuild("✅ Pedido criado com sucesso!" + CR + &#13;
                                   "• ID: %1" + CR + &#13;
                                   "• Cliente: %2" + CR + &#13;
                                   "• Total de itens: %3" + CR + &#13;
                                   "• Valor total: R$ %4", &#13;
                                   sIDPedido,&#13;
                                   resultadoPedido.cliente.nome,&#13;
                                   resultadoPedido.resumo.total_itens,&#13;
                                   resultadoPedido.resumo.valor_total)&#13;
    Info(sResumo)&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 3. CONSULTAR PEDIDO COMPLETO&#13;
    //----------------------------------------------------------------------&#13;
    Info("🔍 Consultando pedido completo...")&#13;
    &#13;
    pedidoDetalhado is Variant = pedidoManager.ObterPedidoCompleto(sIDPedido)&#13;
    &#13;
    sDetalhes is string = "📋 Detalhes do Pedido:" + CR + CR&#13;
    sDetalhes += "Cliente: " + pedidoDetalhado.cliente.nome + " (" + pedidoDetalhado.cliente.email + ")" + CR&#13;
    sDetalhes += "Data: " + pedidoDetalhado.pedido.data_pedido + " " + pedidoDetalhado.pedido.hora_pedido + CR&#13;
    sDetalhes += "Status: " + pedidoDetalhado.pedido.status + CR&#13;
    sDetalhes += "Valor Total: R$ " + pedidoDetalhado.pedido.valor_total + CR + CR&#13;
    sDetalhes += "📦 Itens:" + CR&#13;
    &#13;
    FOR i = 1 TO ArrayCount(pedidoDetalhado.itens)&#13;
        sDetalhes += StringBuild("• %1 (Qtd: %2, Preço: R$ %3, Total: R$ %4)" + CR,&#13;
                                pedidoDetalhado.itens[i].produto_nome,&#13;
                                pedidoDetalhado.itens[i].quantidade,&#13;
                                pedidoDetalhado.itens[i].preco_unitario,&#13;
                                pedidoDetalhado.itens[i].preco_total)&#13;
    END&#13;
    &#13;
    Info(sDetalhes)&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 4. ATUALIZAR STATUS DO PEDIDO&#13;
    //----------------------------------------------------------------------&#13;
    Info("📝 Atualizando status do pedido...")&#13;
    &#13;
    IF pedidoManager.AtualizarStatusPedido(sIDPedido, "CONFIRMADO", "Pedido confirmado após verificação") THEN&#13;
        Info("✅ Status atualizado para CONFIRMADO")&#13;
    END&#13;
    &#13;
    IF pedidoManager.AtualizarStatusPedido(sIDPedido, "ENVIADO", "Pedido enviado via transportadora XYZ") THEN&#13;
        Info("✅ Status atualizado para ENVIADO")&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 5. LISTAR PEDIDOS DO CLIENTE&#13;
    //----------------------------------------------------------------------&#13;
    Info("📋 Listando pedidos do cliente...")&#13;
    &#13;
    pedidosCliente is Variant = pedidoManager.ListarPedidosPorCliente(sIDCliente)&#13;
    Info("📊 Cliente possui " + ArrayCount(pedidosCliente) + " pedido(s)")&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 6. CANCELAR PEDIDO (OPCIONAL)&#13;
    //----------------------------------------------------------------------&#13;
    nResposta is int = YesNo("❓ Deseja cancelar o pedido de teste?")&#13;
    &#13;
    IF nResposta = Yes THEN&#13;
        IF pedidoManager.CancelarPedido(sIDPedido, "Teste concluído") THEN&#13;
            Info("✅ Pedido cancelado e estoque revertido")&#13;
        END&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 7. LIMPEZA DOS DADOS DE TESTE&#13;
    //----------------------------------------------------------------------&#13;
    IF YesNo("🗑️ Excluir dados de teste criados?") = Yes THEN&#13;
        Info("🗑️ Removendo dados de teste...")&#13;
        &#13;
        // Remove itens do pedido&#13;
        itensPedido is Variant = pedidoManager.m_cItemPedidoAPI.GetAll("id_pedido=" + sIDPedido)&#13;
        FOR i = 1 TO ArrayCount(itensPedido)&#13;
            pedidoManager.m_cItemPedidoAPI.Delete(itensPedido[i].id)&#13;
        END&#13;
        &#13;
        // Remove pedido&#13;
        pedidoManager.m_cPedidoAPI.Delete(sIDPedido)&#13;
        &#13;
        // Remove produtos&#13;
        FOR i = 1 TO ArrayCount(arrayProdutosCriados)&#13;
            pedidoManager.m_cProdutoAPI.Delete(arrayProdutosCriados[i])&#13;
        END&#13;
        &#13;
        // Remove cliente&#13;
        pedidoManager.m_cClienteAPI.Delete(sIDCliente)&#13;
        &#13;
        Info("✅ Dados de teste removidos")&#13;
    END&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro durante teste: " + ExceptionInfo())&#13;
END&#13;
&#13;
Info("🎉 Teste do Sistema de Pedidos concluído!")&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// RELATÓRIOS E CONSULTAS AVANÇADAS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// GerarRelatorioPedidos: Gera relatório consolidado de pedidos&#13;
//——————————————————————————&#13;
PROCEDURE GerarRelatorioPedidos(dDataInicio is Date, dDataFim is Date) : string&#13;
pedidoManager is CPedidoManager()&#13;
sRelatorio is string&#13;
&#13;
```&#13;
TRY&#13;
    // Busca pedidos do período&#13;
    pedidosPeriodo is Variant = pedidoManager.ListarPedidosPorPeriodo(dDataInicio, dDataFim)&#13;
    &#13;
    nTotalPedidos is int = ArrayCount(pedidosPeriodo)&#13;
    nValorTotal is currency = 0&#13;
    nPendentes is int = 0&#13;
    nConfirmados is int = 0&#13;
    nEnviados is int = 0&#13;
    nEntregues is int = 0&#13;
    nCancelados is int = 0&#13;
    &#13;
    // Analisa dados&#13;
    FOR i = 1 TO nTotalPedidos&#13;
        nValorTotal += pedidosPeriodo[i].valor_total&#13;
        &#13;
        SWITCH pedidosPeriodo[i].status&#13;
            CASE "PENDENTE": nPendentes++&#13;
            CASE "CONFIRMADO": nConfirmados++&#13;
            CASE "ENVIADO": nEnviados++&#13;
            CASE "ENTREGUE": nEntregues++&#13;
            CASE "CANCELADO": nCancelados++&#13;
        END&#13;
    END&#13;
    &#13;
    // Gera relatório&#13;
    sRelatorio = "📊 RELATÓRIO DE PEDIDOS" + CR&#13;
    sRelatorio += "Período: " + DateToString(dDataInicio, "DD/MM/YYYY") + " a " + DateToString(dDataFim, "DD/MM/YYYY") + CR&#13;
    sRelatorio += StringRepeat("=", 50) + CR + CR&#13;
    &#13;
    sRelatorio += "📈 RESUMO GERAL:" + CR&#13;
    sRelatorio += "• Total de pedidos: " + nTotalPedidos + CR&#13;
    sRelatorio += "• Valor total: R$ " + NumToString(nValorTotal, "999,999.99") + CR&#13;
    sRelatorio += "• Ticket médio: R$ " + IIF(nTotalPedidos &gt; 0, NumToString(nValorTotal / nTotalPedidos, "999.99"), "0.00") + CR + CR&#13;
    &#13;
    sRelatorio += "📊 STATUS DOS PEDIDOS:" + CR&#13;
    sRelatorio += "• Pendentes: " + nPendentes + " (" + NumToString(nPendentes * 100 / nTotalPedidos, "99.9") + "%)" + CR&#13;
    sRelatorio += "• Confirmados: " + nConfirmados + " (" + NumToString(nConfirmados *&#13;
```&#13;
&#13;
[/code]&#13;
&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO COMPLETO: SISTEMA DE PEDIDOS E ITENS&#13;
// Descrição: Demonstra uso prático com relacionamentos mestre-detalhe&#13;
// Tabelas: PEDIDO, ITEM_PEDIDO, CLIENTE, PRODUTO&#13;
// Autor: Sistema Automático&#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
//==============================================================================&#13;
// ESTRUTURA DAS TABELAS (Para referência)&#13;
//==============================================================================&#13;
/*&#13;
CLIENTE:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- nome (Text 100, Mandatory)&#13;
- email (Text 150, Mandatory, Unique)&#13;
- telefone (Text 20)&#13;
- endereco (Text 200)&#13;
- cidade (Text 50)&#13;
- ativo (Boolean, Default True)&#13;
&#13;
PRODUTO:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- nome (Text 100, Mandatory)&#13;
- descricao (Text 500)&#13;
- preco (Currency, Mandatory)&#13;
- estoque (Numeric, Default 0)&#13;
- categoria (Text 50)&#13;
- ativo (Boolean, Default True)&#13;
&#13;
PEDIDO:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- id_cliente (Numeric, Foreign Key -&gt; CLIENTE.id, Mandatory)&#13;
- data_pedido (Date, Mandatory, Default Today)&#13;
- hora_pedido (Time, Default Now)&#13;
- status (Text 20, Default “PENDENTE”) // PENDENTE, CONFIRMADO, ENVIADO, ENTREGUE, CANCELADO&#13;
- valor_total (Currency, Default 0)&#13;
- observacoes (Text 500)&#13;
- data_entrega (Date)&#13;
&#13;
ITEM_PEDIDO:&#13;
&#13;
- id (Numeric, Primary Key, Auto Increment)&#13;
- id_pedido (Numeric, Foreign Key -&gt; PEDIDO.id, Mandatory)&#13;
- id_produto (Numeric, Foreign Key -&gt; PRODUTO.id, Mandatory)&#13;
- quantidade (Numeric, Mandatory, Default 1)&#13;
- preco_unitario (Currency, Mandatory)&#13;
- preco_total (Currency, Calculated: quantidade * preco_unitario)&#13;
- observacoes (Text 200)&#13;
  */&#13;
&#13;
//==============================================================================&#13;
// SERVIDOR: INICIALIZANDO APIS PARA TODAS AS TABELAS&#13;
//==============================================================================&#13;
&#13;
PROCEDURE IniciarServidorPedidos()&#13;
// Gerenciador para todas as tabelas do sistema&#13;
manager is CRestCRUDManager()&#13;
&#13;
```&#13;
Info("🚀 Iniciando Sistema de Pedidos - APIs REST")&#13;
&#13;
// Exibe documentação das tabelas encontradas&#13;
sDoc is string = manager.GetAPIDocumentation()&#13;
Trace("📋 Tabelas do sistema:" + CR + sDoc)&#13;
&#13;
// Inicia servidores para todas as tabelas&#13;
IF manager.StartAllServers() THEN&#13;
    Info("✅ Servidor de Pedidos iniciado com sucesso!" + CR + CR + ...&#13;
         "🌐 APIs disponíveis:" + CR + ...&#13;
         "• CLIENTE: http://localhost:8080/api/v1/cliente" + CR + ...&#13;
         "• PRODUTO: http://localhost:8081/api/v1/produto" + CR + ...&#13;
         "• PEDIDO: http://localhost:8082/api/v1/pedido" + CR + ...&#13;
         "• ITEM_PEDIDO: http://localhost:8083/api/v1/item_pedido" + CR + CR + ...&#13;
         "📖 Consulte API_Documentation.md para detalhes")&#13;
    &#13;
    // Mantém servidores ativos&#13;
    Info("⚡ Servidores rodando... Pressione OK para parar.")&#13;
ELSE&#13;
    Error("❌ Falha ao iniciar servidores!")&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CPedidoManager&#13;
// Descrição: Gerenciador de alto nível para operações de pedidos&#13;
//==============================================================================&#13;
&#13;
CPedidoManager is Class&#13;
PRIVATE&#13;
m_cClienteAPI is CRestCRUDClient&#13;
m_cProdutoAPI is CRestCRUDClient  &#13;
m_cPedidoAPI is CRestCRUDClient&#13;
m_cItemPedidoAPI is CRestCRUDClient&#13;
m_sBaseURL is string&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa todos os clientes API&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sBaseURL is string = “http://localhost”)&#13;
m_sBaseURL = sBaseURL&#13;
&#13;
```&#13;
// Inicializa clientes para cada tabela&#13;
m_cClienteAPI = new CRestCRUDClient(sBaseURL + ":8080/api/v1", "cliente")&#13;
m_cProdutoAPI = new CRestCRUDClient(sBaseURL + ":8081/api/v1", "produto") &#13;
m_cPedidoAPI = new CRestCRUDClient(sBaseURL + ":8082/api/v1", "pedido")&#13;
m_cItemPedidoAPI = new CRestCRUDClient(sBaseURL + ":8083/api/v1", "item_pedido")&#13;
&#13;
// Timeout de 60 segundos para operações complexas&#13;
m_cClienteAPI.SetTimeout(60000)&#13;
m_cProdutoAPI.SetTimeout(60000)&#13;
m_cPedidoAPI.SetTimeout(60000)&#13;
m_cItemPedidoAPI.SetTimeout(60000)&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS DE NEGÓCIO - PEDIDOS COMPLETOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// CriarPedidoCompleto: Cria pedido com itens em uma transação&#13;
//——————————————————————————&#13;
PROCEDURE CriarPedidoCompleto(varPedidoCompleto is Variant) : Variant&#13;
varPedidoCriado is Variant&#13;
varResult is Variant&#13;
arrayItens is array of Variant&#13;
nValorTotal is currency = 0&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    Trace("🛒 Iniciando criação de pedido completo...")&#13;
    &#13;
    // 1. Valida se cliente existe&#13;
    varCliente is Variant = m_cClienteAPI.GetByID(varPedidoCompleto.id_cliente)&#13;
    IF VariantToJSON(varCliente) = "null" THEN&#13;
        ExceptionThrow(1, "Cliente não encontrado: " + varPedidoCompleto.id_cliente)&#13;
    END&#13;
    &#13;
    Trace("✅ Cliente validado: " + varCliente.nome)&#13;
    &#13;
    // 2. Valida produtos e calcula valor total&#13;
    IF VariantExists(varPedidoCompleto, "itens") THEN&#13;
        arrayItens = varPedidoCompleto.itens&#13;
        &#13;
        FOR i = 1 TO ArrayCount(arrayItens)&#13;
            // Valida produto&#13;
            varProduto is Variant = m_cProdutoAPI.GetByID(arrayItens[i].id_produto)&#13;
            IF VariantToJSON(varProduto) = "null" THEN&#13;
                ExceptionThrow(2, "Produto não encontrado: " + arrayItens[i].id_produto)&#13;
            END&#13;
            &#13;
            // Valida estoque&#13;
            IF varProduto.estoque &lt; arrayItens[i].quantidade THEN&#13;
                ExceptionThrow(3, "Estoque insuficiente para produto: " + varProduto.nome + &#13;
                                " (Disponível: " + varProduto.estoque + &#13;
                                ", Solicitado: " + arrayItens[i].quantidade + ")")&#13;
            END&#13;
            &#13;
            // Usa preço atual do produto se não informado&#13;
            IF NOT VariantExists(arrayItens[i], "preco_unitario") THEN&#13;
                arrayItens[i].preco_unitario = varProduto.preco&#13;
            END&#13;
            &#13;
            // Calcula preço total do item&#13;
            arrayItens[i].preco_total = arrayItens[i].quantidade * arrayItens[i].preco_unitario&#13;
            nValorTotal += arrayItens[i].preco_total&#13;
            &#13;
            Trace("✅ Item validado: " + varProduto.nome + &#13;
                  " (Qtd: " + arrayItens[i].quantidade + &#13;
                  ", Valor: " + arrayItens[i].preco_total + ")")&#13;
        END&#13;
    END&#13;
    &#13;
    // 3. Cria o pedido (cabeçalho)&#13;
    varNovoPedido is Variant&#13;
    varNovoPedido.id_cliente = varPedidoCompleto.id_cliente&#13;
    varNovoPedido.data_pedido = IIF(VariantExists(varPedidoCompleto, "data_pedido"), &#13;
                                   varPedidoCompleto.data_pedido, &#13;
                                   DateToString(Today(), "YYYY-MM-DD"))&#13;
    varNovoPedido.hora_pedido = IIF(VariantExists(varPedidoCompleto, "hora_pedido"), &#13;
                                   varPedidoCompleto.hora_pedido, &#13;
                                   TimeToString(Now(), "HH:MM:SS"))&#13;
    varNovoPedido.status = IIF(VariantExists(varPedidoCompleto, "status"), &#13;
                              varPedidoCompleto.status, &#13;
                              "PENDENTE")&#13;
    varNovoPedido.valor_total = nValorTotal&#13;
    varNovoPedido.observacoes = IIF(VariantExists(varPedidoCompleto, "observacoes"), &#13;
                                   varPedidoCompleto.observacoes, &#13;
                                   "")&#13;
    &#13;
    varPedidoCriado = m_cPedidoAPI.Create(varNovoPedido)&#13;
    &#13;
    Trace("✅ Pedido criado - ID: " + varPedidoCriado.id + ", Valor: " + varPedidoCriado.valor_total)&#13;
    &#13;
    // 4. Cria os itens do pedido&#13;
    arrayItensCriados is array of Variant&#13;
    &#13;
    FOR i = 1 TO ArrayCount(arrayItens)&#13;
        varNovoItem is Variant&#13;
        varNovoItem.id_pedido = varPedidoCriado.id&#13;
        varNovoItem.id_produto = arrayItens[i].id_produto&#13;
        varNovoItem.quantidade = arrayItens[i].quantidade&#13;
        varNovoItem.preco_unitario = arrayItens[i].preco_unitario&#13;
        varNovoItem.preco_total = arrayItens[i].preco_total&#13;
        varNovoItem.observacoes = IIF(VariantExists(arrayItens[i], "observacoes"), &#13;
                                     arrayItens[i].observacoes, &#13;
                                     "")&#13;
        &#13;
        varItemCriado is Variant = m_cItemPedidoAPI.Create(varNovoItem)&#13;
        ArrayAdd(arrayItensCriados, varItemCriado)&#13;
        &#13;
        // Atualiza estoque do produto&#13;
        AtualizarEstoque(arrayItens[i].id_produto, -arrayItens[i].quantidade)&#13;
        &#13;
        Trace("✅ Item criado - ID: " + varItemCriado.id + &#13;
              ", Produto: " + arrayItens[i].id_produto + &#13;
              ", Quantidade: " + arrayItens[i].quantidade)&#13;
    END&#13;
    &#13;
    // 5. Monta resultado completo&#13;
    varResult.pedido = varPedidoCriado&#13;
    varResult.itens = arrayItensCriados&#13;
    varResult.cliente = varCliente&#13;
    varResult.resumo.total_itens = ArrayCount(arrayItensCriados)&#13;
    varResult.resumo.valor_total = nValorTotal&#13;
    &#13;
    Trace("🎉 Pedido completo criado com sucesso!")&#13;
    &#13;
    RESULT varResult&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao criar pedido: " + ExceptionInfo())&#13;
    &#13;
    // Se houve erro e pedido foi criado, tenta cancelar&#13;
    IF VariantExists(varPedidoCriado, "id") THEN&#13;
        CancelarPedido(varPedidoCriado.id, "Erro durante criação")&#13;
    END&#13;
    &#13;
    RESULT Null&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ObterPedidoCompleto: Busca pedido com todos os dados relacionados&#13;
//——————————————————————————&#13;
PROCEDURE ObterPedidoCompleto(sIDPedido is string) : Variant&#13;
varResult is Variant&#13;
varPedido is Variant&#13;
varCliente is Variant&#13;
arrayItens is array of Variant&#13;
arrayItensDetalhados is array of Variant&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    // 1. Busca dados do pedido&#13;
    varPedido = m_cPedidoAPI.GetByID(sIDPedido)&#13;
    IF VariantToJSON(varPedido) = "null" THEN&#13;
        ExceptionThrow(1, "Pedido não encontrado: " + sIDPedido)&#13;
    END&#13;
    &#13;
    // 2. Busca dados do cliente&#13;
    varCliente = m_cClienteAPI.GetByID(varPedido.id_cliente)&#13;
    &#13;
    // 3. Busca itens do pedido&#13;
    arrayItens = m_cItemPedidoAPI.GetAll("id_pedido=" + sIDPedido)&#13;
    &#13;
    // 4. Enriquece itens com dados dos produtos&#13;
    FOR i = 1 TO ArrayCount(arrayItens)&#13;
        varItemDetalhado is Variant = arrayItens[i]&#13;
        &#13;
        // Busca dados do produto&#13;
        varProduto is Variant = m_cProdutoAPI.GetByID(arrayItens[i].id_produto)&#13;
        &#13;
        // Adiciona informações do produto ao item&#13;
        varItemDetalhado.produto_nome = varProduto.nome&#13;
        varItemDetalhado.produto_descricao = varProduto.descricao&#13;
        varItemDetalhado.produto_categoria = varProduto.categoria&#13;
        varItemDetalhado.produto_estoque_atual = varProduto.estoque&#13;
        &#13;
        ArrayAdd(arrayItensDetalhados, varItemDetalhado)&#13;
    END&#13;
    &#13;
    // 5. Monta resultado completo&#13;
    varResult.pedido = varPedido&#13;
    varResult.cliente = varCliente&#13;
    varResult.itens = arrayItensDetalhados&#13;
    varResult.resumo.total_itens = ArrayCount(arrayItensDetalhados)&#13;
    varResult.resumo.valor_total = varPedido.valor_total&#13;
    &#13;
    RESULT varResult&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao obter pedido completo: " + ExceptionInfo())&#13;
    RESULT Null&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AtualizarStatusPedido: Atualiza status do pedido&#13;
//——————————————————————————&#13;
PROCEDURE AtualizarStatusPedido(sIDPedido is string, sNovoStatus is string, sObservacao is string = “”) : boolean&#13;
varDados is Variant&#13;
&#13;
```&#13;
TRY&#13;
    varDados.status = sNovoStatus&#13;
    &#13;
    IF sObservacao &lt;&gt; "" THEN&#13;
        // Busca observações atuais&#13;
        varPedido is Variant = m_cPedidoAPI.GetByID(sIDPedido)&#13;
        sObservacaoAtual is string = IIF(VariantExists(varPedido, "observacoes"), varPedido.observacoes, "")&#13;
        &#13;
        // Adiciona nova observação com timestamp&#13;
        sTimestamp is string = DateTimeToString(Now(), "DD/MM/YYYY HH:MM:SS")&#13;
        varDados.observacoes = sObservacaoAtual + CR + "[" + sTimestamp + "] Status: " + sNovoStatus + " - " + sObservacao&#13;
    END&#13;
    &#13;
    varAtualizado is Variant = m_cPedidoAPI.Update(sIDPedido, varDados)&#13;
    &#13;
    Trace("✅ Status do pedido " + sIDPedido + " atualizado para: " + sNovoStatus)&#13;
    RESULT True&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao atualizar status: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CancelarPedido: Cancela pedido e reverte estoque&#13;
//——————————————————————————&#13;
PROCEDURE CancelarPedido(sIDPedido is string, sMotivo is string = “”) : boolean&#13;
arrayItens is array of Variant&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    // 1. Busca itens do pedido para reverter estoque&#13;
    arrayItens = m_cItemPedidoAPI.GetAll("id_pedido=" + sIDPedido)&#13;
    &#13;
    // 2. Reverte estoque de cada item&#13;
    FOR i = 1 TO ArrayCount(arrayItens)&#13;
        AtualizarEstoque(arrayItens[i].id_produto, arrayItens[i].quantidade)&#13;
        Trace("↩️ Estoque revertido - Produto: " + arrayItens[i].id_produto + ", Qtd: " + arrayItens[i].quantidade)&#13;
    END&#13;
    &#13;
    // 3. Atualiza status do pedido&#13;
    sObservacaoCancelamento is string = "Pedido cancelado"&#13;
    IF sMotivo &lt;&gt; "" THEN&#13;
        sObservacaoCancelamento += " - Motivo: " + sMotivo&#13;
    END&#13;
    &#13;
    RESULT AtualizarStatusPedido(sIDPedido, "CANCELADO", sObservacaoCancelamento)&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao cancelar pedido: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// MÉTODOS AUXILIARES&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// AtualizarEstoque: Atualiza estoque do produto&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE AtualizarEstoque(sIDProduto is string, nQuantidade is int) : boolean&#13;
TRY&#13;
// Busca produto atual&#13;
varProduto is Variant = m_cProdutoAPI.GetByID(sIDProduto)&#13;
&#13;
```&#13;
    // Calcula novo estoque&#13;
    nNovoEstoque is int = varProduto.estoque + nQuantidade&#13;
    &#13;
    IF nNovoEstoque &lt; 0 THEN&#13;
        ExceptionThrow(1, "Estoque não pode ficar negativo")&#13;
    END&#13;
    &#13;
    // Atualiza estoque&#13;
    varDados is Variant&#13;
    varDados.estoque = nNovoEstoque&#13;
    &#13;
    m_cProdutoAPI.Update(sIDProduto, varDados)&#13;
    &#13;
    Trace("📦 Estoque atualizado - Produto: " + sIDProduto + &#13;
          ", Anterior: " + varProduto.estoque + &#13;
          ", Novo: " + nNovoEstoque)&#13;
    &#13;
    RESULT True&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao atualizar estoque: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ListarPedidosPorCliente: Lista pedidos de um cliente&#13;
//——————————————————————————&#13;
PROCEDURE ListarPedidosPorCliente(sIDCliente is string, sStatus is string = “”) : Variant&#13;
sFilter is string = “id_cliente=” + sIDCliente&#13;
&#13;
```&#13;
IF sStatus &lt;&gt; "" THEN&#13;
    sFilter += " AND status='" + sStatus + "'"&#13;
END&#13;
&#13;
RESULT m_cPedidoAPI.GetAll(sFilter, 100, 0)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ListarPedidosPorPeriodo: Lista pedidos por período&#13;
//——————————————————————————&#13;
PROCEDURE ListarPedidosPorPeriodo(dDataInicio is Date, dDataFim is Date) : Variant&#13;
sFilter is string = StringBuild(“data_pedido&gt;=’%1’ AND data_pedido&lt;=’%2’”,&#13;
DateToString(dDataInicio, “YYYY-MM-DD”),&#13;
DateToString(dDataFim, “YYYY-MM-DD”))&#13;
&#13;
```&#13;
RESULT m_cPedidoAPI.GetAll(sFilter, 1000, 0)&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE USO PRÁTICO&#13;
//==============================================================================&#13;
&#13;
PROCEDURE TestarSistemaPedidos()&#13;
// Inicializa gerenciador&#13;
pedidoManager is CPedidoManager()&#13;
&#13;
```&#13;
Info("🧪 Iniciando teste do Sistema de Pedidos...")&#13;
&#13;
TRY&#13;
    //----------------------------------------------------------------------&#13;
    // 1. CRIAR DADOS DE TESTE&#13;
    //----------------------------------------------------------------------&#13;
    Info("📝 Criando dados de teste...")&#13;
    &#13;
    // Criar cliente de teste&#13;
    novoCliente is Variant&#13;
    novoCliente.nome = "João Silva"&#13;
    novoCliente.email = "joao.teste@email.com"&#13;
    novoCliente.telefone = "11999887766"&#13;
    novoCliente.endereco = "Rua das Flores, 123"&#13;
    novoCliente.cidade = "São Paulo"&#13;
    &#13;
    clienteCriado is Variant = pedidoManager.m_cClienteAPI.Create(novoCliente)&#13;
    sIDCliente is string = clienteCriado.id&#13;
    &#13;
    // Criar produtos de teste&#13;
    arrayProdutosCriados is array of string&#13;
    &#13;
    FOR i = 1 TO 3&#13;
        novoProduto is Variant&#13;
        novoProduto.nome = "Produto Teste " + i&#13;
        novoProduto.descricao = "Descrição do produto " + i&#13;
        novoProduto.preco = 25.50 * i&#13;
        novoProduto.estoque = 100&#13;
        novoProduto.categoria = "Eletrônicos"&#13;
        &#13;
        produtoCriado is Variant = pedidoManager.m_cProdutoAPI.Create(novoProduto)&#13;
        ArrayAdd(arrayProdutosCriados, produtoCriado.id)&#13;
    END&#13;
    &#13;
    Info("✅ Dados de teste criados - Cliente: " + sIDCliente + ", Produtos: " + ArrayCount(arrayProdutosCriados))&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 2. CRIAR PEDIDO COMPLETO&#13;
    //----------------------------------------------------------------------&#13;
    Info("🛒 Criando pedido completo...")&#13;
    &#13;
    pedidoCompleto is Variant&#13;
    pedidoCompleto.id_cliente = sIDCliente&#13;
    pedidoCompleto.observacoes = "Pedido de teste criado via API"&#13;
    &#13;
    // Itens do pedido&#13;
    arrayItens is array of Variant&#13;
    &#13;
    FOR i = 1 TO ArrayCount(arrayProdutosCriados)&#13;
        novoItem is Variant&#13;
        novoItem.id_produto = arrayProdutosCriados[i]&#13;
        novoItem.quantidade = i * 2  // 2, 4, 6&#13;
        novoItem.observacoes = "Item " + i + " do pedido de teste"&#13;
        &#13;
        ArrayAdd(arrayItens, novoItem)&#13;
    END&#13;
    &#13;
    pedidoCompleto.itens = arrayItens&#13;
    &#13;
    // Cria pedido&#13;
    resultadoPedido is Variant = pedidoManager.CriarPedidoCompleto(pedidoCompleto)&#13;
    sIDPedido is string = resultadoPedido.pedido.id&#13;
    &#13;
    sResumo is string = StringBuild("✅ Pedido criado com sucesso!" + CR + &#13;
                                   "• ID: %1" + CR + &#13;
                                   "• Cliente: %2" + CR + &#13;
                                   "• Total de itens: %3" + CR + &#13;
                                   "• Valor total: R$ %4", &#13;
                                   sIDPedido,&#13;
                                   resultadoPedido.cliente.nome,&#13;
                                   resultadoPedido.resumo.total_itens,&#13;
                                   resultadoPedido.resumo.valor_total)&#13;
    Info(sResumo)&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 3. CONSULTAR PEDIDO COMPLETO&#13;
    //----------------------------------------------------------------------&#13;
    Info("🔍 Consultando pedido completo...")&#13;
    &#13;
    pedidoDetalhado is Variant = pedidoManager.ObterPedidoCompleto(sIDPedido)&#13;
    &#13;
    sDetalhes is string = "📋 Detalhes do Pedido:" + CR + CR&#13;
    sDetalhes += "Cliente: " + pedidoDetalhado.cliente.nome + " (" + pedidoDetalhado.cliente.email + ")" + CR&#13;
    sDetalhes += "Data: " + pedidoDetalhado.pedido.data_pedido + " " + pedidoDetalhado.pedido.hora_pedido + CR&#13;
    sDetalhes += "Status: " + pedidoDetalhado.pedido.status + CR&#13;
    sDetalhes += "Valor Total: R$ " + pedidoDetalhado.pedido.valor_total + CR + CR&#13;
    sDetalhes += "📦 Itens:" + CR&#13;
    &#13;
    FOR i = 1 TO ArrayCount(pedidoDetalhado.itens)&#13;
        sDetalhes += StringBuild("• %1 (Qtd: %2, Preço: R$ %3, Total: R$ %4)" + CR,&#13;
                                pedidoDetalhado.itens[i].produto_nome,&#13;
                                pedidoDetalhado.itens[i].quantidade,&#13;
                                pedidoDetalhado.itens[i].preco_unitario,&#13;
                                pedidoDetalhado.itens[i].preco_total)&#13;
    END&#13;
    &#13;
    Info(sDetalhes)&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 4. ATUALIZAR STATUS DO PEDIDO&#13;
    //----------------------------------------------------------------------&#13;
    Info("📝 Atualizando status do pedido...")&#13;
    &#13;
    IF pedidoManager.AtualizarStatusPedido(sIDPedido, "CONFIRMADO", "Pedido confirmado após verificação") THEN&#13;
        Info("✅ Status atualizado para CONFIRMADO")&#13;
    END&#13;
    &#13;
    IF pedidoManager.AtualizarStatusPedido(sIDPedido, "ENVIADO", "Pedido enviado via transportadora XYZ") THEN&#13;
        Info("✅ Status atualizado para ENVIADO")&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 5. LISTAR PEDIDOS DO CLIENTE&#13;
    //----------------------------------------------------------------------&#13;
    Info("📋 Listando pedidos do cliente...")&#13;
    &#13;
    pedidosCliente is Variant = pedidoManager.ListarPedidosPorCliente(sIDCliente)&#13;
    Info("📊 Cliente possui " + ArrayCount(pedidosCliente) + " pedido(s)")&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 6. CANCELAR PEDIDO (OPCIONAL)&#13;
    //----------------------------------------------------------------------&#13;
    nResposta is int = YesNo("❓ Deseja cancelar o pedido de teste?")&#13;
    &#13;
    IF nResposta = Yes THEN&#13;
        IF pedidoManager.CancelarPedido(sIDPedido, "Teste concluído") THEN&#13;
            Info("✅ Pedido cancelado e estoque revertido")&#13;
        END&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 7. LIMPEZA DOS DADOS DE TESTE&#13;
    //----------------------------------------------------------------------&#13;
    IF YesNo("🗑️ Excluir dados de teste criados?") = Yes THEN&#13;
        Info("🗑️ Removendo dados de teste...")&#13;
        &#13;
        // Remove itens do pedido&#13;
        itensPedido is Variant = pedidoManager.m_cItemPedidoAPI.GetAll("id_pedido=" + sIDPedido)&#13;
        FOR i = 1 TO ArrayCount(itensPedido)&#13;
            pedidoManager.m_cItemPedidoAPI.Delete(itensPedido[i].id)&#13;
        END&#13;
        &#13;
        // Remove pedido&#13;
        pedidoManager.m_cPedidoAPI.Delete(sIDPedido)&#13;
        &#13;
        // Remove produtos&#13;
        FOR i = 1 TO ArrayCount(arrayProdutosCriados)&#13;
            pedidoManager.m_cProdutoAPI.Delete(arrayProdutosCriados[i])&#13;
        END&#13;
        &#13;
        // Remove cliente&#13;
        pedidoManager.m_cClienteAPI.Delete(sIDCliente)&#13;
        &#13;
        Info("✅ Dados de teste removidos")&#13;
    END&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro durante teste: " + ExceptionInfo())&#13;
END&#13;
&#13;
Info("🎉 Teste do Sistema de Pedidos concluído!")&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// RELATÓRIOS E CONSULTAS AVANÇADAS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// GerarRelatorioPedidos: Gera relatório consolidado de pedidos&#13;
//——————————————————————————&#13;
PROCEDURE GerarRelatorioPedidos(dDataInicio is Date, dDataFim is Date) : string&#13;
pedidoManager is CPedidoManager()&#13;
sRelatorio is string&#13;
&#13;
```&#13;
TRY&#13;
    // Busca pedidos do período&#13;
    pedidosPeriodo is Variant = pedidoManager.ListarPedidosPorPeriodo(dDataInicio, dDataFim)&#13;
    &#13;
    nTotalPedidos is int = ArrayCount(pedidosPeriodo)&#13;
    nValorTotal is currency = 0&#13;
    nPendentes is int = 0&#13;
    nConfirmados is int = 0&#13;
    nEnviados is int = 0&#13;
    nEntregues is int = 0&#13;
    nCancelados is int = 0&#13;
    &#13;
    // Analisa dados&#13;
    FOR i = 1 TO nTotalPedidos&#13;
        nValorTotal += pedidosPeriodo[i].valor_total&#13;
        &#13;
        SWITCH pedidosPeriodo[i].status&#13;
            CASE "PENDENTE": nPendentes++&#13;
            CASE "CONFIRMADO": nConfirmados++&#13;
            CASE "ENVIADO": nEnviados++&#13;
            CASE "ENTREGUE": nEntregues++&#13;
            CASE "CANCELADO": nCancelados++&#13;
        END&#13;
    END&#13;
    &#13;
    // Gera relatório&#13;
    sRelatorio = "📊 RELATÓRIO DE PEDIDOS" + CR&#13;
    sRelatorio += "Período: " + DateToString(dDataInicio, "DD/MM/YYYY") + " a " + DateToString(dDataFim, "DD/MM/YYYY") + CR&#13;
    sRelatorio += StringRepeat("=", 50) + CR + CR&#13;
    &#13;
    sRelatorio += "📈 RESUMO GERAL:" + CR&#13;
    sRelatorio += "• Total de pedidos: " + nTotalPedidos + CR&#13;
    sRelatorio += "• Valor total: R$ " + NumToString(nValorTotal, "999,999.99") + CR&#13;
    sRelatorio += "• Ticket médio: R$ " + IIF(nTotalPedidos &gt; 0, NumToString(nValorTotal / nTotalPedidos, "999.99"), "0.00") + CR + CR&#13;
    &#13;
    sRelatorio += "📊 STATUS DOS PEDIDOS:" + CR&#13;
    sRelatorio += "• Pendentes: " + nPendentes + " (" + NumToString(nPendentes * 100 / nTotalPedidos, "99.9") + "%)" + CR&#13;
    sRelatorio += "• Confirmados: " + nConfirmados + " (" + NumToString(nConfirmados * 100 / nTotalPedidos, "99.9") + "%)" + CR&#13;
    sRelatorio += "• Enviados: " + nEnviados + " (" + NumToString(nEnviados * 100 / nTotalPedidos, "99.9") + "%)" + CR&#13;
    sRelatorio += "• Entregues: " + nEntregues + " (" + NumToString(nEntregues * 100 / nTotalPedidos, "99.9") + "%)" + CR&#13;
    sRelatorio += "• Cancelados: " + nCancelados + " (" + NumToString(nCancelados * 100 / nTotalPedidos, "99.9") + "%)" + CR + CR&#13;
    &#13;
    RESULT sRelatorio&#13;
    &#13;
EXCEPTION&#13;
    Error("❌ Erro ao gerar relatório: " + ExceptionInfo())&#13;
    RESULT "Erro ao gerar relatório"&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CPedidoWebService&#13;
// Descrição: WebService específico para operações de pedidos&#13;
//==============================================================================&#13;
&#13;
CPedidoWebService is Class&#13;
PRIVATE&#13;
m_cPedidoManager is CPedidoManager&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Constructor&#13;
//——————————————————————————&#13;
PROCEDURE Constructor()&#13;
m_cPedidoManager = new CPedidoManager()&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ProcessarPedidoWeb: Endpoint personalizado para pedidos via web&#13;
//——————————————————————————&#13;
PROCEDURE ProcessarPedidoWeb()&#13;
sJSONInput is string&#13;
varPedidoCompleto is Variant&#13;
varResultado is Variant&#13;
sResponse is string&#13;
&#13;
```&#13;
// Configurar CORS&#13;
WebWriteHTTPHeader("Access-Control-Allow-Origin", "*")&#13;
WebWriteHTTPHeader("Access-Control-Allow-Methods", "POST, OPTIONS")&#13;
WebWriteHTTPHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")&#13;
&#13;
TRY&#13;
    // Trata OPTIONS (preflight)&#13;
    IF WebRequestMethod() = "OPTIONS" THEN&#13;
        WebWriteHTTPCode(204)&#13;
        RETURN&#13;
    END&#13;
    &#13;
    // Obtém dados do pedido&#13;
    sJSONInput = WebReadRequestBody()&#13;
    &#13;
    IF sJSONInput = "" THEN&#13;
        WebWriteHTTPCode(400)&#13;
        WebWriteString("{\"error\": \"Dados do pedido não informados\"}", "application/json")&#13;
        RETURN&#13;
    END&#13;
    &#13;
    // Converte JSON para Variant&#13;
    JSONToVariant(varPedidoCompleto, sJSONInput)&#13;
    &#13;
    // Valida dados obrigatórios&#13;
    IF NOT VariantExists(varPedidoCompleto, "id_cliente") THEN&#13;
        WebWriteHTTPCode(400)&#13;
        WebWriteString("{\"error\": \"ID do cliente é obrigatório\"}", "application/json")&#13;
        RETURN&#13;
    END&#13;
    &#13;
    IF NOT VariantExists(varPedidoCompleto, "itens") OR ArrayCount(varPedidoCompleto.itens) = 0 THEN&#13;
        WebWriteHTTPCode(400)&#13;
        WebWriteString("{\"error\": \"Pelo menos um item é obrigatório\"}", "application/json")&#13;
        RETURN&#13;
    END&#13;
    &#13;
    // Processa pedido&#13;
    varResultado = m_cPedidoManager.CriarPedidoCompleto(varPedidoCompleto)&#13;
    &#13;
    IF VariantToJSON(varResultado) = "null" THEN&#13;
        WebWriteHTTPCode(500)&#13;
        WebWriteString("{\"error\": \"Erro interno ao processar pedido\"}", "application/json")&#13;
        RETURN&#13;
    END&#13;
    &#13;
    // Retorna sucesso&#13;
    sResponse = VariantToJSON(varResultado)&#13;
    WebWriteHTTPCode(201)&#13;
    WebWriteString(sResponse, "application/json")&#13;
    &#13;
EXCEPTION&#13;
    WebWriteHTTPCode(500)&#13;
    WebWriteString("{\"error\": \"" + Replace(ExceptionInfo(), """", "\""") + "\"}", "application/json")&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ConsultarPedidoWeb: Endpoint para consulta de pedido&#13;
//——————————————————————————&#13;
PROCEDURE ConsultarPedidoWeb()&#13;
sIDPedido is string&#13;
varResultado is Variant&#13;
sResponse is string&#13;
&#13;
```&#13;
// Configurar CORS&#13;
WebWriteHTTPHeader("Access-Control-Allow-Origin", "*")&#13;
WebWriteHTTPHeader("Access-Control-Allow-Methods", "GET, OPTIONS")&#13;
WebWriteHTTPHeader("Access-Control-Allow-Headers", "Content-Type")&#13;
&#13;
TRY&#13;
    IF WebRequestMethod() = "OPTIONS" THEN&#13;
        WebWriteHTTPCode(204)&#13;
        RETURN&#13;
    END&#13;
    &#13;
    sIDPedido = WebParameter("id", parameterURL)&#13;
    &#13;
    IF sIDPedido = "" THEN&#13;
        WebWriteHTTPCode(400)&#13;
        WebWriteString("{\"error\": \"ID do pedido não informado\"}", "application/json")&#13;
        RETURN&#13;
    END&#13;
    &#13;
    varResultado = m_cPedidoManager.ObterPedidoCompleto(sIDPedido)&#13;
    &#13;
    IF VariantToJSON(varResultado) = "null" THEN&#13;
        WebWriteHTTPCode(404)&#13;
        WebWriteString("{\"error\": \"Pedido não encontrado\"}", "application/json")&#13;
        RETURN&#13;
    END&#13;
    &#13;
    sResponse = VariantToJSON(varResultado)&#13;
    WebWriteHTTPCode(200)&#13;
    WebWriteString(sResponse, "application/json")&#13;
    &#13;
EXCEPTION&#13;
    WebWriteHTTPCode(500)&#13;
    WebWriteString("{\"error\": \"" + Replace(ExceptionInfo(), """", "\""") + "\"}", "application/json")&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// INTEGRAÇÃO COM E-COMMERCE&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// ProcessarPedidoEcommerce: Processa pedido vindo de e-commerce&#13;
//——————————————————————————&#13;
PROCEDURE ProcessarPedidoEcommerce(sJSONPedido is string) : string&#13;
varPedidoEcommerce is Variant&#13;
varPedidoInterno is Variant&#13;
varResultado is Variant&#13;
pedidoManager is CPedidoManager()&#13;
i is int&#13;
&#13;
```&#13;
TRY&#13;
    // Parse do JSON do e-commerce&#13;
    JSONToVariant(varPedidoEcommerce, sJSONPedido)&#13;
    &#13;
    // Mapeia dados do e-commerce para formato interno&#13;
    varPedidoInterno.id_cliente = ObterOuCriarCliente(varPedidoEcommerce.cliente)&#13;
    varPedidoInterno.observacoes = "Pedido E-commerce #" + varPedidoEcommerce.numero_pedido&#13;
    &#13;
    // Mapeia itens&#13;
    arrayItensInternos is array of Variant&#13;
    &#13;
    FOR i = 1 TO ArrayCount(varPedidoEcommerce.itens)&#13;
        varItemInterno is Variant&#13;
        varItemInterno.id_produto = ObterProdutoPorSKU(varPedidoEcommerce.itens[i].sku)&#13;
        varItemInterno.quantidade = varPedidoEcommerce.itens[i].quantidade&#13;
        varItemInterno.preco_unitario = varPedidoEcommerce.itens[i].preco&#13;
        varItemInterno.observacoes = "SKU: " + varPedidoEcommerce.itens[i].sku&#13;
        &#13;
        ArrayAdd(arrayItensInternos, varItemInterno)&#13;
    END&#13;
    &#13;
    varPedidoInterno.itens = arrayItensInternos&#13;
    &#13;
    // Cria pedido&#13;
    varResultado = pedidoManager.CriarPedidoCompleto(varPedidoInterno)&#13;
    &#13;
    // Retorna resposta para e-commerce&#13;
    varResposta is Variant&#13;
    varResposta.sucesso = True&#13;
    varResposta.id_pedido_interno = varResultado.pedido.id&#13;
    varResposta.numero_pedido_ecommerce = varPedidoEcommerce.numero_pedido&#13;
    varResposta.valor_total = varResultado.resumo.valor_total&#13;
    varResposta.mensagem = "Pedido processado com sucesso"&#13;
    &#13;
    RESULT VariantToJSON(varResposta)&#13;
    &#13;
EXCEPTION&#13;
    // Retorna erro para e-commerce&#13;
    varErro is Variant&#13;
    varErro.sucesso = False&#13;
    varErro.erro = ExceptionInfo()&#13;
    varErro.numero_pedido_ecommerce = IIF(VariantExists(varPedidoEcommerce, "numero_pedido"), &#13;
                                          varPedidoEcommerce.numero_pedido, &#13;
                                          "N/A")&#13;
    &#13;
    RESULT VariantToJSON(varErro)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ObterOuCriarCliente: Obtém cliente existente ou cria novo&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ObterOuCriarCliente(varClienteEcommerce is Variant) : string&#13;
clienteAPI is CRestCRUDClient(“http://localhost:8080/api/v1”, “cliente”)&#13;
&#13;
```&#13;
TRY&#13;
    // Busca cliente por email&#13;
    clientesEncontrados is Variant = clienteAPI.GetAll("email='" + varClienteEcommerce.email + "'")&#13;
    &#13;
    IF ArrayCount(clientesEncontrados) &gt; 0 THEN&#13;
        // Cliente existe, retorna ID&#13;
        RESULT clientesEncontrados[1].id&#13;
    ELSE&#13;
        // Cria novo cliente&#13;
        novoCliente is Variant&#13;
        novoCliente.nome = varClienteEcommerce.nome&#13;
        novoCliente.email = varClienteEcommerce.email&#13;
        novoCliente.telefone = IIF(VariantExists(varClienteEcommerce, "telefone"), varClienteEcommerce.telefone, "")&#13;
        novoCliente.endereco = IIF(VariantExists(varClienteEcommerce, "endereco"), varClienteEcommerce.endereco, "")&#13;
        novoCliente.cidade = IIF(VariantExists(varClienteEcommerce, "cidade"), varClienteEcommerce.cidade, "")&#13;
        &#13;
        clienteCriado is Variant = clienteAPI.Create(novoCliente)&#13;
        RESULT clienteCriado.id&#13;
    END&#13;
    &#13;
EXCEPTION&#13;
    ExceptionThrow(1, "Erro ao obter/criar cliente: " + ExceptionInfo())&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ObterProdutoPorSKU: Obtém ID do produto pelo SKU&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ObterProdutoPorSKU(sSKU is string) : string&#13;
produtoAPI is CRestCRUDClient(“http://localhost:8081/api/v1”, “produto”)&#13;
&#13;
```&#13;
TRY&#13;
    // Busca produto pelo SKU (assumindo que existe campo SKU)&#13;
    produtosEncontrados is Variant = produtoAPI.GetAll("sku='" + sSKU + "'")&#13;
    &#13;
    IF ArrayCount(produtosEncontrados) &gt; 0 THEN&#13;
        RESULT produtosEncontrados[1].id&#13;
    ELSE&#13;
        ExceptionThrow(1, "Produto não encontrado para SKU: " + sSKU)&#13;
    END&#13;
    &#13;
EXCEPTION&#13;
    ExceptionThrow(2, "Erro ao buscar produto por SKU: " + ExceptionInfo())&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE INTEGRAÇÃO COM JAVASCRIPT/WEB&#13;
//==============================================================================&#13;
&#13;
/*&#13;
EXEMPLO DE CÓDIGO JAVASCRIPT PARA CONSUMIR A API:&#13;
&#13;
// Função para criar pedido via JavaScript&#13;
async function criarPedido() {&#13;
const pedido = {&#13;
id_cliente: “1”,&#13;
observacoes: “Pedido criado via JavaScript”,&#13;
itens: [&#13;
{&#13;
id_produto: “1”,&#13;
quantidade: 2,&#13;
observacoes: “Item 1”&#13;
},&#13;
{&#13;
id_produto: “2”,&#13;
quantidade: 1,&#13;
preco_unitario: 99.90,&#13;
observacoes: “Item 2 com preço especial”&#13;
}&#13;
]&#13;
};&#13;
&#13;
```&#13;
try {&#13;
    const response = await fetch('http://localhost:8082/api/v1/pedido-completo', {&#13;
        method: 'POST',&#13;
        headers: {&#13;
            'Content-Type': 'application/json'&#13;
        },&#13;
        body: JSON.stringify(pedido)&#13;
    });&#13;
    &#13;
    if (response.ok) {&#13;
        const resultado = await response.json();&#13;
        console.log('✅ Pedido criado:', resultado);&#13;
        &#13;
        // Exibe resultado&#13;
        document.getElementById('resultado').innerHTML = `&#13;
            &lt;h3&gt;Pedido Criado com Sucesso!&lt;/h3&gt;&#13;
            &lt;p&gt;&lt;strong&gt;ID:&lt;/strong&gt; ${resultado.pedido.id}&lt;/p&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Cliente:&lt;/strong&gt; ${resultado.cliente.nome}&lt;/p&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Total de Itens:&lt;/strong&gt; ${resultado.resumo.total_itens}&lt;/p&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Valor Total:&lt;/strong&gt; R$ ${resultado.resumo.valor_total}&lt;/p&gt;&#13;
        `;&#13;
    } else {&#13;
        const erro = await response.json();&#13;
        console.error('❌ Erro:', erro);&#13;
        alert('Erro ao criar pedido: ' + erro.error);&#13;
    }&#13;
} catch (error) {&#13;
    console.error('❌ Erro de rede:', error);&#13;
    alert('Erro de conexão: ' + error.message);&#13;
}&#13;
```&#13;
&#13;
}&#13;
&#13;
// Função para consultar pedido&#13;
async function consultarPedido(idPedido) {&#13;
try {&#13;
const response = await fetch(`http://localhost:8082/api/v1/pedido-completo/${idPedido}`);&#13;
&#13;
```&#13;
    if (response.ok) {&#13;
        const pedido = await response.json();&#13;
        console.log('📋 Pedido encontrado:', pedido);&#13;
        &#13;
        // Monta HTML com detalhes&#13;
        let html = `&#13;
            &lt;h3&gt;Detalhes do Pedido #${pedido.pedido.id}&lt;/h3&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Cliente:&lt;/strong&gt; ${pedido.cliente.nome} (${pedido.cliente.email})&lt;/p&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Data:&lt;/strong&gt; ${pedido.pedido.data_pedido} ${pedido.pedido.hora_pedido}&lt;/p&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; ${pedido.pedido.status}&lt;/p&gt;&#13;
            &lt;p&gt;&lt;strong&gt;Valor Total:&lt;/strong&gt; R$ ${pedido.pedido.valor_total}&lt;/p&gt;&#13;
            &lt;h4&gt;Itens:&lt;/h4&gt;&#13;
            &lt;ul&gt;&#13;
        `;&#13;
        &#13;
        pedido.itens.forEach(item =&gt; {&#13;
            html += `&#13;
                &lt;li&gt;&#13;
                    ${item.produto_nome} - &#13;
                    Qtd: ${item.quantidade} - &#13;
                    Preço: R$ ${item.preco_unitario} - &#13;
                    Total: R$ ${item.preco_total}&#13;
                &lt;/li&gt;&#13;
            `;&#13;
        });&#13;
        &#13;
        html += '&lt;/ul&gt;';&#13;
        document.getElementById('detalhes').innerHTML = html;&#13;
        &#13;
    } else {&#13;
        alert('Pedido não encontrado');&#13;
    }&#13;
} catch (error) {&#13;
    console.error('❌ Erro:', error);&#13;
    alert('Erro ao consultar pedido: ' + error.message);&#13;
}&#13;
```&#13;
&#13;
}&#13;
*/&#13;
&#13;
//==============================================================================&#13;
// CONFIGURAÇÃO DO SERVIDOR WEB PERSONALIZADO&#13;
//==============================================================================&#13;
&#13;
PROCEDURE ConfigurarServidorPedidos()&#13;
// Cria instância do WebService&#13;
pedidoWS is CPedidoWebService()&#13;
&#13;
```&#13;
// Configura rotas personalizadas&#13;
WebAddRoute("/api/v1/pedido-completo", httpPost, pedidoWS.ProcessarPedidoWeb)&#13;
WebAddRoute("/api/v1/pedido-completo", httpOptions, pedidoWS.ProcessarPedidoWeb)&#13;
WebAddRoute("/api/v1/pedido-completo/{id}", httpGet, pedidoWS.ConsultarPedidoWeb)&#13;
WebAddRoute("/api/v1/pedido-completo/{id}", httpOptions, pedidoWS.ConsultarPedidoWeb)&#13;
&#13;
// Inicia servidor na porta 8090 para endpoints personalizados&#13;
IF WebServerStart(8090) THEN&#13;
    Info("🌐 Servidor de Pedidos Personalizados iniciado na porta 8090" + CR + CR + &#13;
         "Endpoints disponíveis:" + CR + &#13;
         "• POST http://localhost:8090/api/v1/pedido-completo" + CR + &#13;
         "• GET  http://localhost:8090/api/v1/pedido-completo/{id}")&#13;
ELSE&#13;
    Error("❌ Falha ao iniciar servidor personalizado")&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE JSON PARA TESTE&#13;
//==============================================================================&#13;
&#13;
/*&#13;
EXEMPLO DE JSON PARA CRIAR PEDIDO:&#13;
&#13;
{&#13;
“id_cliente”: “1”,&#13;
“observacoes”: “Pedido via API REST”,&#13;
“itens”: [&#13;
{&#13;
“id_produto”: “1”,&#13;
“quantidade”: 2,&#13;
“observacoes”: “Produto principal”&#13;
},&#13;
{&#13;
“id_produto”: “2”,&#13;
“quantidade”: 1,&#13;
“preco_unitario”: 99.90,&#13;
“observacoes”: “Produto com desconto”&#13;
},&#13;
{&#13;
“id_produto”: “3”,&#13;
“quantidade”: 3&#13;
}&#13;
]&#13;
}&#13;
&#13;
RESPOSTA ESPERADA:&#13;
&#13;
{&#13;
“pedido”: {&#13;
“id”: “15”,&#13;
“id_cliente”: “1”,&#13;
“data_pedido”: “2025-08-14”,&#13;
“hora_pedido”: “14:30:25”,&#13;
“status”: “PENDENTE”,&#13;
“valor_total”: 285.30,&#13;
“observacoes”: “Pedido via API REST”&#13;
},&#13;
“cliente”: {&#13;
“id”: “1”,&#13;
“nome”: “João Silva”,&#13;
“email”: “joao@email.com”&#13;
},&#13;
“itens”: [&#13;
{&#13;
“id”: “45”,&#13;
“id_pedido”: “15”,&#13;
“id_produto”: “1”,&#13;
“quantidade”: 2,&#13;
“preco_unitario”: 50.00,&#13;
“preco_total”: 100.00,&#13;
“produto_nome”: “Produto A”&#13;
},&#13;
{&#13;
“id”: “46”,&#13;
“id_pedido”: “15”,&#13;
“id_produto”: “2”,&#13;
“quantidade”: 1,&#13;
“preco_unitario”: 99.90,&#13;
“preco_total”: 99.90,&#13;
“produto_nome”: “Produto B”&#13;
}&#13;
],&#13;
“resumo”: {&#13;
“total_itens”: 2,&#13;
“valor_total”: 285.30&#13;
}&#13;
}&#13;
*/&#13;
&#13;
&#13;
&#13;
&#13;
📋 Exemplo Completo de Sistema de Pedidos e Itens&#13;
🏗️ Estrutura do Sistema:&#13;
	1.	4 Tabelas principais:&#13;
	•	CLIENTE - Dados dos clientes&#13;
	•	PRODUTO - Catálogo de produtos&#13;
	•	PEDIDO - Cabeçalho dos pedidos&#13;
	•	ITEM_PEDIDO - Itens de cada pedido&#13;
	2.	3 Classes especializadas:&#13;
	•	CPedidoManager - Operações de negócio de alto nível&#13;
	•	CPedidoWebService - Endpoints web personalizados&#13;
	•	APIs CRUD automáticas para cada tabela&#13;
🚀 Funcionalidades Implementadas:&#13;
✅ Operações Completas de Pedido:&#13;
	•	Criação transacional - Pedido + itens em uma operação&#13;
	•	Validação automática - Cliente, produtos, estoque&#13;
	•	Cálculo automático - Preços e totais&#13;
	•	Controle de estoque - Baixa automática e reversão&#13;
	•	Consulta enriquecida - Pedido com dados relacionados&#13;
✅ Gestão de Status:&#13;
	•	Fluxo completo: PENDENTE → CONFIRMADO → ENVIADO → ENTREGUE&#13;
	•	Cancelamento inteligente - Reverte estoque automaticamente&#13;
	•	Histórico de alterações - Log com timestamps&#13;
✅ Integrações:&#13;
	•	E-commerce - Processa pedidos externos&#13;
	•	JavaScript/Web - APIs prontas para frontend&#13;
	•	Relatórios - Análises automáticas&#13;
&#13;
🎯 Casos de Uso Práticos:&#13;
&#13;
1. Criação de Pedido Simples:&#13;
&#13;
pedidoManager is CPedidoManager()&#13;
&#13;
// Monta pedido&#13;
pedidoCompleto is Variant&#13;
pedidoCompleto.id_cliente = "1"&#13;
pedidoCompleto.observacoes = "Pedido urgente"&#13;
&#13;
// Adiciona itens&#13;
arrayItens is array of Variant&#13;
novoItem is Variant&#13;
novoItem.id_produto = "5"&#13;
novoItem.quantidade = 3&#13;
ArrayAdd(arrayItens, novoItem)&#13;
&#13;
pedidoCompleto.itens = arrayItens&#13;
&#13;
// Cria pedido completo&#13;
resultado is Variant = pedidoManager.CriarPedidoCompleto(pedidoCompleto)&#13;
&#13;
&#13;
&#13;
2. Integração com E-commerce:&#13;
&#13;
sJSONEcommerce = `{&#13;
    "numero_pedido": "EC-2025-001",&#13;
    "cliente": {&#13;
        "nome": "Maria Silva", &#13;
        "email": "maria@email.com"&#13;
    },&#13;
    "itens": [&#13;
        {"sku": "PROD-001", "quantidade": 2, "preco": 50.00}&#13;
    ]&#13;
}`&#13;
&#13;
sResposta = ProcessarPedidoEcommerce(sJSONEcommerce)&#13;
&#13;
&#13;
&#13;
3. Consulta Avançada:&#13;
&#13;
// Busca pedido com todos os detalhes&#13;
pedidoCompleto is Variant = pedidoManager.ObterPedidoCompleto("15")&#13;
&#13;
// Dados disponíveis:&#13;
// pedidoCompleto.pedido - Dados do pedido&#13;
// pedidoCompleto.cliente - Dados do cliente  &#13;
// pedidoCompleto.itens[i].produto_nome - Nome dos produtos&#13;
// pedidoCompleto.resumo.valor_total - Totais calculados&#13;
&#13;
&#13;
&#13;
🌐 APIs REST Disponíveis:&#13;
APIs Básicas (CRUD automático):&#13;
	•	http://localhost:8080/api/v1/cliente&#13;
	•	http://localhost:8081/api/v1/produto&#13;
	•	http://localhost:8082/api/v1/pedido&#13;
	•	http://localhost:8083/api/v1/item_pedido&#13;
APIs Especializadas:&#13;
	•	POST http://localhost:8090/api/v1/pedido-completo - Criar pedido completo&#13;
	•	GET http://localhost:8090/api/v1/pedido-completo/{id} - Consultar com detalhes&#13;
📊 Recursos Avançados:&#13;
	•	Relatórios automáticos com estatísticas&#13;
	•	Controle transacional - Rollback em caso de erro&#13;
	•	Validações de negócio - Estoque, cliente, produtos&#13;
	•	Logs detalhados - Rastreabilidade completa&#13;
	•	CORS configurado - Pronto para web&#13;
	•	Tratamento de erros robusto&#13;
&#13;
🔧 Fácil de Usar:&#13;
&#13;
Para iniciar tudo:&#13;
&#13;
// Inicia todas as APIs básicas&#13;
IniciarServidorPedidos()&#13;
&#13;
// Inicia APIs personalizadas  &#13;
ConfigurarServidorPedidos()&#13;
&#13;
// Testa o sistema completo&#13;
TestarSistemaPedidos()&#13;
&#13;
&#13;
Este exemplo demonstra como o sistema CRUD REST API em WLanguage pode ser usado para casos reais complexos, mantendo a simplicidade de uso e máximo reaproveitamento de código! 🎯​​​​​​​​​​​​​​​​&#13;
&#13;
&#13;
//==============================================================================&#13;
// RECURSOS AVANÇADOS E EXTENSÕES DO SISTEMA CRUD REST API&#13;
// Descrição: Cache, Autenticação, Validações, Hooks, Performance&#13;
// Autor: Sistema Automático  &#13;
// Data: 14/08/2025&#13;
//==============================================================================&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestCacheManager&#13;
// Descrição: Gerenciamento de cache para otimização de performance&#13;
//==============================================================================&#13;
&#13;
CRestCacheManager is Class&#13;
PRIVATE&#13;
m_stCache is array of stCacheEntry&#13;
m_nMaxEntries is int = 1000&#13;
m_nDefaultTTL is int = 300 // 5 minutos em segundos&#13;
END&#13;
&#13;
stCacheEntry is Structure&#13;
sKey is string&#13;
varData is Variant&#13;
dtExpiry is DateTime&#13;
nHits is int&#13;
dtLastAccess is DateTime&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Get: Obtém item do cache&#13;
//——————————————————————————&#13;
PROCEDURE Get(sKey is string) : Variant&#13;
i is int&#13;
dtNow is DateTime = Now()&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stCache)&#13;
    IF m_stCache[i].sKey = sKey THEN&#13;
        // Verifica se não expirou&#13;
        IF m_stCache[i].dtExpiry &gt; dtNow THEN&#13;
            m_stCache[i].nHits++&#13;
            m_stCache[i].dtLastAccess = dtNow&#13;
            Trace("🎯 Cache HIT: " + sKey)&#13;
            RESULT m_stCache[i].varData&#13;
        ELSE&#13;
            // Remove item expirado&#13;
            ArrayDelete(m_stCache, i)&#13;
            Trace("⏰ Cache EXPIRED: " + sKey)&#13;
            BREAK&#13;
        END&#13;
    END&#13;
END&#13;
&#13;
Trace("❌ Cache MISS: " + sKey)&#13;
RESULT Null&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Set: Armazena item no cache&#13;
//——————————————————————————&#13;
PROCEDURE Set(sKey is string, varData is Variant, nTTLSeconds is int = 0)&#13;
stEntry is stCacheEntry&#13;
dtNow is DateTime = Now()&#13;
&#13;
```&#13;
// Remove entrada existente&#13;
RemoveByKey(sKey)&#13;
&#13;
// Limpa cache se estiver cheio&#13;
IF ArrayCount(m_stCache) &gt;= m_nMaxEntries THEN&#13;
    CleanupOldEntries()&#13;
END&#13;
&#13;
// Adiciona nova entrada&#13;
stEntry.sKey = sKey&#13;
stEntry.varData = varData&#13;
stEntry.dtExpiry = DateTimeAdd(dtNow, IIF(nTTLSeconds &gt; 0, nTTLSeconds, m_nDefaultTTL), "s")&#13;
stEntry.nHits = 0&#13;
stEntry.dtLastAccess = dtNow&#13;
&#13;
ArrayAdd(m_stCache, stEntry)&#13;
Trace("✅ Cache SET: " + sKey + " (TTL: " + IIF(nTTLSeconds &gt; 0, nTTLSeconds, m_nDefaultTTL) + "s)")&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// InvalidatePattern: Invalida itens por padrão&#13;
//——————————————————————————&#13;
PROCEDURE InvalidatePattern(sPattern is string)&#13;
i is int = 1&#13;
nRemoved is int = 0&#13;
&#13;
```&#13;
WHILE i &lt;= ArrayCount(m_stCache)&#13;
    IF Position(m_stCache[i].sKey, sPattern) &gt; 0 THEN&#13;
        ArrayDelete(m_stCache, i)&#13;
        nRemoved++&#13;
    ELSE&#13;
        i++&#13;
    END&#13;
END&#13;
&#13;
Trace("🗑️ Cache invalidated: " + nRemoved + " entries matching '" + sPattern + "'")&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// RemoveByKey: Remove item específico do cache&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE RemoveByKey(sKey is string)&#13;
i is int&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stCache)&#13;
    IF m_stCache[i].sKey = sKey THEN&#13;
        ArrayDelete(m_stCache, i)&#13;
        BREAK&#13;
    END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CleanupOldEntries: Remove entradas antigas para liberar espaço&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE CleanupOldEntries()&#13;
dtNow is DateTime = Now()&#13;
i is int = 1&#13;
nRemoved is int = 0&#13;
&#13;
```&#13;
// Remove entradas expiradas primeiro&#13;
WHILE i &lt;= ArrayCount(m_stCache)&#13;
    IF m_stCache[i].dtExpiry &lt;= dtNow THEN&#13;
        ArrayDelete(m_stCache, i)&#13;
        nRemoved++&#13;
    ELSE&#13;
        i++&#13;
    END&#13;
END&#13;
&#13;
// Se ainda estiver cheio, remove as menos acessadas&#13;
IF ArrayCount(m_stCache) &gt;= m_nMaxEntries THEN&#13;
    ArraySort(m_stCache, asSortByMember, "dtLastAccess")&#13;
    &#13;
    WHILE ArrayCount(m_stCache) &gt;= m_nMaxEntries * 0.8&#13;
        ArrayDelete(m_stCache, 1)&#13;
        nRemoved++&#13;
    END&#13;
END&#13;
&#13;
Trace("🧹 Cache cleanup: " + nRemoved + " entries removed")&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestAuthManager  &#13;
// Descrição: Gerenciamento de autenticação e autorização&#13;
//==============================================================================&#13;
&#13;
CRestAuthManager is Class&#13;
PRIVATE&#13;
m_stTokens is array of stAuthToken&#13;
m_stUsers is array of stAuthUser&#13;
m_sJWTSecret is string = “MinhaChaveSecreta123!”&#13;
m_nTokenExpiry is int = 3600 // 1 hora&#13;
END&#13;
&#13;
stAuthToken is Structure&#13;
sToken is string&#13;
sUserID is string&#13;
dtExpiry is DateTime&#13;
sPermissions is string&#13;
END&#13;
&#13;
stAuthUser is Structure&#13;
sUserID is string&#13;
sUsername is string&#13;
sPasswordHash is string&#13;
sPermissions is string&#13;
bActive is boolean&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AuthenticateUser: Autentica usuário e gera token&#13;
//——————————————————————————&#13;
PROCEDURE AuthenticateUser(sUsername is string, sPassword is string) : string&#13;
i is int&#13;
sPasswordHash is string&#13;
sToken is string&#13;
stToken is stAuthToken&#13;
&#13;
```&#13;
TRY&#13;
    // Hash da senha fornecida&#13;
    sPasswordHash = Encrypt(sPassword, encryptMD5)&#13;
    &#13;
    // Busca usuário&#13;
    FOR i = 1 TO ArrayCount(m_stUsers)&#13;
        IF m_stUsers[i].sUsername = sUsername AND m_stUsers[i].sPasswordHash = sPasswordHash AND m_stUsers[i].bActive THEN&#13;
            // Gera token JWT&#13;
            sToken = GenerateJWTToken(m_stUsers[i].sUserID, m_stUsers[i].sPermissions)&#13;
            &#13;
            // Armazena token&#13;
            stToken.sToken = sToken&#13;
            stToken.sUserID = m_stUsers[i].sUserID&#13;
            stToken.dtExpiry = DateTimeAdd(Now(), m_nTokenExpiry, "s")&#13;
            stToken.sPermissions = m_stUsers[i].sPermissions&#13;
            &#13;
            ArrayAdd(m_stTokens, stToken)&#13;
            &#13;
            Trace("✅ User authenticated: " + sUsername + " (Token: " + Left(sToken, 20) + "...)")&#13;
            RESULT sToken&#13;
        END&#13;
    END&#13;
    &#13;
    Trace("❌ Authentication failed: " + sUsername)&#13;
    RESULT ""&#13;
    &#13;
EXCEPTION&#13;
    Error("Erro na autenticação: " + ExceptionInfo())&#13;
    RESULT ""&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ValidateToken: Valida token de acesso&#13;
//——————————————————————————&#13;
PROCEDURE ValidateToken(sToken is string) : stAuthToken&#13;
i is int&#13;
dtNow is DateTime = Now()&#13;
stEmptyToken is stAuthToken&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stTokens)&#13;
    IF m_stTokens[i].sToken = sToken THEN&#13;
        IF m_stTokens[i].dtExpiry &gt; dtNow THEN&#13;
            Trace("✅ Token valid: " + Left(sToken, 20) + "...")&#13;
            RESULT m_stTokens[i]&#13;
        ELSE&#13;
            // Remove token expirado&#13;
            ArrayDelete(m_stTokens, i)&#13;
            Trace("⏰ Token expired: " + Left(sToken, 20) + "...")&#13;
            BREAK&#13;
        END&#13;
    END&#13;
END&#13;
&#13;
Trace("❌ Token invalid: " + Left(sToken, 20) + "...")&#13;
RESULT stEmptyToken&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CheckPermission: Verifica se usuário tem permissão&#13;
//——————————————————————————&#13;
PROCEDURE CheckPermission(sToken is string, sRequiredPermission is string) : boolean&#13;
stToken is stAuthToken = ValidateToken(sToken)&#13;
&#13;
```&#13;
IF stToken.sUserID = "" THEN&#13;
    RESULT False&#13;
END&#13;
&#13;
// Verifica se tem permissão específica ou é admin&#13;
IF Position(stToken.sPermissions, sRequiredPermission) &gt; 0 OR Position(stToken.sPermissions, "admin") &gt; 0 THEN&#13;
    RESULT True&#13;
END&#13;
&#13;
Trace("🚫 Permission denied: " + sRequiredPermission + " for user " + stToken.sUserID)&#13;
RESULT False&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CreateUser: Cria novo usuário&#13;
//——————————————————————————&#13;
PROCEDURE CreateUser(sUsername is string, sPassword is string, sPermissions is string = “read”) : boolean&#13;
stUser is stAuthUser&#13;
&#13;
```&#13;
TRY&#13;
    stUser.sUserID = GenerateGUID()&#13;
    stUser.sUsername = sUsername&#13;
    stUser.sPasswordHash = Encrypt(sPassword, encryptMD5)&#13;
    stUser.sPermissions = sPermissions&#13;
    stUser.bActive = True&#13;
    &#13;
    ArrayAdd(m_stUsers, stUser)&#13;
    &#13;
    Trace("✅ User created: " + sUsername + " (Permissions: " + sPermissions + ")")&#13;
    RESULT True&#13;
    &#13;
EXCEPTION&#13;
    Error("Erro ao criar usuário: " + ExceptionInfo())&#13;
    RESULT False&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GenerateJWTToken: Gera token JWT simples&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE GenerateJWTToken(sUserID is string, sPermissions is string) : string&#13;
sHeader is string = “{"typ":"JWT","alg":"HS256"}”&#13;
sPayload is string = StringBuild(”{"sub":"%1","permissions":"%2","exp":%3,"iat":%4}”,&#13;
sUserID,&#13;
sPermissions,&#13;
DateTimeToInteger(DateTimeAdd(Now(), m_nTokenExpiry, “s”)),&#13;
DateTimeToInteger(Now()))&#13;
&#13;
```&#13;
sHeaderB64 is string = Encode(sHeader, encodeBASE64URL)&#13;
sPayloadB64 is string = Encode(sPayload, encodeBASE64URL)&#13;
&#13;
sSignature is string = Encrypt(sHeaderB64 + "." + sPayloadB64 + "." + m_sJWTSecret, encryptSHA256)&#13;
sSignatureB64 is string = Encode(sSignature, encodeBASE64URL)&#13;
&#13;
RESULT sHeaderB64 + "." + sPayloadB64 + "." + sSignatureB64&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestValidationManager&#13;
// Descrição: Validações customizadas e regras de negócio&#13;
//==============================================================================&#13;
&#13;
CRestValidationManager is Class&#13;
PRIVATE&#13;
m_stRules is array of stValidationRule&#13;
END&#13;
&#13;
stValidationRule is Structure&#13;
sTable is string&#13;
sField is string&#13;
sRuleType is string // required, email, numeric, date, custom&#13;
sCustomFunction is string&#13;
sErrorMessage is string&#13;
bActive is boolean&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// AddRule: Adiciona regra de validação&#13;
//——————————————————————————&#13;
PROCEDURE AddRule(sTable is string, sField is string, sRuleType is string, sErrorMessage is string, sCustomFunction is string = “”)&#13;
stRule is stValidationRule&#13;
&#13;
```&#13;
stRule.sTable = Upper(sTable)&#13;
stRule.sField = Upper(sField)&#13;
stRule.sRuleType = sRuleType&#13;
stRule.sCustomFunction = sCustomFunction&#13;
stRule.sErrorMessage = sErrorMessage&#13;
stRule.bActive = True&#13;
&#13;
ArrayAdd(m_stRules, stRule)&#13;
&#13;
Trace("📋 Validation rule added: " + sTable + "." + sField + " (" + sRuleType + ")")&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ValidateRecord: Valida registro contra regras&#13;
//——————————————————————————&#13;
PROCEDURE ValidateRecord(sTable is string, varRecord is Variant, arrayErrors is array of string) : boolean&#13;
i is int&#13;
bValid is boolean = True&#13;
sValue is string&#13;
&#13;
```&#13;
ArrayDeleteAll(arrayErrors)&#13;
&#13;
FOR i = 1 TO ArrayCount(m_stRules)&#13;
    IF m_stRules[i].bActive AND Upper(m_stRules[i].sTable) = Upper(sTable) THEN&#13;
        &#13;
        IF VariantExists(varRecord, m_stRules[i].sField) THEN&#13;
            sValue = varRecord[m_stRules[i].sField]&#13;
        ELSE&#13;
            sValue = ""&#13;
        END&#13;
        &#13;
        IF NOT ValidateField(sValue, m_stRules[i]) THEN&#13;
            ArrayAdd(arrayErrors, m_stRules[i].sErrorMessage)&#13;
            bValid = False&#13;
        END&#13;
    END&#13;
END&#13;
&#13;
RESULT bValid&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ValidateField: Valida campo individual&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE ValidateField(sValue is string, stRule is stValidationRule) : boolean&#13;
SWITCH stRule.sRuleType&#13;
CASE “required”&#13;
RESULT sValue &lt;&gt; “”&#13;
&#13;
```&#13;
    CASE "email"&#13;
        RESULT EmailValid(sValue)&#13;
        &#13;
    CASE "numeric"&#13;
        RESULT IsNumeric(sValue)&#13;
        &#13;
    CASE "date"&#13;
        RESULT DateValid(sValue)&#13;
        &#13;
    CASE "custom"&#13;
        IF stRule.sCustomFunction &lt;&gt; "" THEN&#13;
            // Executa função customizada usando indirection&#13;
            RESULT {stRule.sCustomFunction}(sValue)&#13;
        END&#13;
        RESULT True&#13;
        &#13;
    DEFAULT&#13;
        RESULT True&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE: CRestHookManager&#13;
// Descrição: Sistema de hooks para interceptar operações CRUD&#13;
//==============================================================================&#13;
&#13;
CRestHookManager is Class&#13;
PRIVATE&#13;
m_stHooks is array of stHook&#13;
END&#13;
&#13;
stHook is Structure&#13;
sTable is string&#13;
sOperation is string // CREATE, READ, UPDATE, DELETE&#13;
sWhen is string // BEFORE, AFTER&#13;
sFunctionName is string&#13;
bActive is boolean&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// RegisterHook: Registra hook para interceptar operações&#13;
//——————————————————————————&#13;
PROCEDURE RegisterHook(sTable is string, sOperation is string, sWhen is string, sFunctionName is string)&#13;
stHook is stHook&#13;
&#13;
```&#13;
stHook.sTable = Upper(sTable)&#13;
stHook.sOperation = Upper(sOperation)&#13;
stHook.sWhen = Upper(sWhen)&#13;
stHook.sFunctionName = sFunctionName&#13;
stHook.bActive = True&#13;
&#13;
ArrayAdd(m_stHooks, stHook)&#13;
&#13;
Trace("🎣 Hook registered: " + sTable + "." + sOperation + "." + sWhen + " -&gt; " + sFunctionName)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// ExecuteHooks: Executa hooks para operação&#13;
//——————————————————————————&#13;
PROCEDURE ExecuteHooks(sTable is string, sOperation is string, sWhen is string, varData is Variant) : boolean&#13;
i is int&#13;
bResult is boolean = True&#13;
&#13;
```&#13;
FOR i = 1 TO ArrayCount(m_stHooks)&#13;
    IF m_stHooks[i].bActive AND &#13;
       Upper(m_stHooks[i].sTable) = Upper(sTable) AND&#13;
       Upper(m_stHooks[i].sOperation) = Upper(sOperation) AND&#13;
       Upper(m_stHooks[i].sWhen) = Upper(sWhen) THEN&#13;
        &#13;
        TRY&#13;
            // Executa hook usando indirection&#13;
            bResult = {m_stHooks[i].sFunctionName}(sTable, sOperation, sWhen, varData)&#13;
            &#13;
            IF NOT bResult THEN&#13;
                Trace("🚫 Hook blocked operation: " + m_stHooks[i].sFunctionName)&#13;
                BREAK&#13;
            END&#13;
            &#13;
        EXCEPTION&#13;
            Error("Erro no hook " + m_stHooks[i].sFunctionName + ": " + ExceptionInfo())&#13;
            bResult = False&#13;
            BREAK&#13;
        END&#13;
    END&#13;
END&#13;
&#13;
RESULT bResult&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// CLASSE ESTENDIDA: CRestCRUDServerAdvanced&#13;
// Descrição: Servidor CRUD com recursos avançados&#13;
//==============================================================================&#13;
&#13;
CRestCRUDServerAdvanced is Class inherits from CRestCRUDServer&#13;
PRIVATE&#13;
m_cCache is CRestCacheManager&#13;
m_cAuth is CRestAuthManager&#13;
m_cValidation is CRestValidationManager&#13;
m_cHooks is CRestHookManager&#13;
m_bCacheEnabled is boolean = True&#13;
m_bAuthRequired is boolean = False&#13;
m_bValidationEnabled is boolean = True&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// Constructor: Inicializa servidor avançado&#13;
//——————————————————————————&#13;
PROCEDURE Constructor(sTableOrQuery is string, sConnection is string = “”)&#13;
// Chama constructor da classe pai&#13;
Ancestor:Constructor(sTableOrQuery, sConnection)&#13;
&#13;
```&#13;
// Inicializa componentes avançados&#13;
m_cCache = new CRestCacheManager()&#13;
m_cAuth = new CRestAuthManager()&#13;
m_cValidation = new CRestValidationManager()&#13;
m_cHooks = new CRestHookManager()&#13;
&#13;
// Configura validações padrão&#13;
SetupDefaultValidations()&#13;
&#13;
// Cria usuário admin padrão&#13;
m_cAuth.CreateUser("admin", "admin123", "admin,read,write,delete")&#13;
m_cAuth.CreateUser("guest", "guest", "read")&#13;
&#13;
Trace("🚀 Advanced CRUD Server initialized for: " + sTableOrQuery)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// EnableCache: Habilita/desabilita cache&#13;
//——————————————————————————&#13;
PROCEDURE EnableCache(bEnable is boolean)&#13;
m_bCacheEnabled = bEnable&#13;
Trace(“🎯 Cache “ + IIF(bEnable, “enabled”, “disabled”))&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// RequireAuth: Habilita/desabilita autenticação&#13;
//——————————————————————————&#13;
PROCEDURE RequireAuth(bRequire is boolean)&#13;
m_bAuthRequired = bRequire&#13;
Trace(“🔐 Authentication “ + IIF(bRequire, “required”, “optional”))&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// GetAllRecordsAdvanced: GET com recursos avançados&#13;
//——————————————————————————&#13;
PROCEDURE GetAllRecordsAdvanced()&#13;
sToken is string&#13;
sCacheKey is string&#13;
varCachedData is Variant&#13;
sJSON is string&#13;
&#13;
```&#13;
// Verifica autenticação se necessária&#13;
IF m_bAuthRequired THEN&#13;
    sToken = WebParameter("Authorization", parameterHTTPHeader)&#13;
    sToken = Replace(sToken, "Bearer ", "")&#13;
    &#13;
    IF NOT m_cAuth.CheckPermission(sToken, "read") THEN&#13;
        HandleAPIError("Acesso negado", 401)&#13;
        RETURN&#13;
    END&#13;
END&#13;
&#13;
// Verifica cache se habilitado&#13;
IF m_bCacheEnabled THEN&#13;
    sCacheKey = "GET_ALL_" + m_sTableName + "_" + WebParameter("filter", parameterGet) + "_" + WebParameter("limit", parameterGet) + "_" + WebParameter("offset", parameterGet)&#13;
    varCachedData = m_cCache.Get(sCacheKey)&#13;
    &#13;
    IF VariantToJSON(varCachedData) &lt;&gt; "null" THEN&#13;
        SetCORSHeaders()&#13;
        WebWriteHTTPCode(200)&#13;
        WebWriteHTTPHeader("X-Cache", "HIT")&#13;
        WebWriteString(varCachedData, "application/json")&#13;
        RETURN&#13;
    END&#13;
END&#13;
&#13;
// Executa hooks BEFORE&#13;
varHookData is Variant&#13;
IF NOT m_cHooks.ExecuteHooks(m_sTableName, "READ", "BEFORE", varHookData) THEN&#13;
    HandleAPIError("Operação bloqueada por hook", 403)&#13;
    RETURN&#13;
END&#13;
&#13;
// Chama método original&#13;
GetAllRecords()&#13;
&#13;
// Cache da resposta se habilitado&#13;
IF m_bCacheEnabled THEN&#13;
    // Obter resposta gerada (simplificado)&#13;
    sJSON = GenerateRecordsJSON(WebParameter("filter", parameterGet), Val(WebParameter("limit", parameterGet, "100")), Val(WebParameter("offset", parameterGet, "0")))&#13;
    m_cCache.Set(sCacheKey, sJSON, 300) // 5 minutos&#13;
    WebWriteHTTPHeader("X-Cache", "MISS")&#13;
END&#13;
&#13;
// Executa hooks AFTER&#13;
m_cHooks.ExecuteHooks(m_sTableName, "READ", "AFTER", varHookData)&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// CreateRecordAdvanced: POST com validações e hooks&#13;
//——————————————————————————&#13;
PROCEDURE CreateRecordAdvanced()&#13;
sToken is string&#13;
sJSONInput is string&#13;
varRecord is Variant&#13;
arrayErrors is array of string&#13;
&#13;
```&#13;
// Verifica autenticação&#13;
IF m_bAuthRequired THEN&#13;
    sToken = WebParameter("Authorization", parameterHTTPHeader)&#13;
    sToken = Replace(sToken, "Bearer ", "")&#13;
    &#13;
    IF NOT m_cAuth.CheckPermission(sToken, "write") THEN&#13;
        HandleAPIError("Acesso negado", 401)&#13;
        RETURN&#13;
    END&#13;
END&#13;
&#13;
TRY&#13;
    sJSONInput = WebReadRequestBody()&#13;
    JSONToVariant(varRecord, sJSONInput)&#13;
    &#13;
    // Executa hooks BEFORE&#13;
    IF NOT m_cHooks.ExecuteHooks(m_sTableName, "CREATE", "BEFORE", varRecord) THEN&#13;
        HandleAPIError("Operação bloqueada por hook", 403)&#13;
        RETURN&#13;
    END&#13;
    &#13;
    // Validações customizadas&#13;
    IF m_bValidationEnabled THEN&#13;
        IF NOT m_cValidation.ValidateRecord(m_sTableName, varRecord, arrayErrors) THEN&#13;
            sErrorMessage is string = "Erro de validação:" + CR&#13;
            FOR i = 1 TO ArrayCount(arrayErrors)&#13;
                sErrorMessage += "• " + arrayErrors[i] + CR&#13;
            END&#13;
            &#13;
            HandleAPIError(sErrorMessage, 400)&#13;
            RETURN&#13;
        END&#13;
    END&#13;
    &#13;
    // Cria registro (método original)&#13;
    sResponse is string = InsertRecord(varRecord)&#13;
    &#13;
    // Invalida cache&#13;
    IF m_bCacheEnabled THEN&#13;
        m_cCache.InvalidatePattern(m_sTableName)&#13;
    END&#13;
    &#13;
    // Executa hooks AFTER&#13;
    JSONToVariant(varRecord, sResponse)&#13;
    m_cHooks.ExecuteHooks(m_sTableName, "CREATE", "AFTER", varRecord)&#13;
    &#13;
    SetCORSHeaders()&#13;
    WebWriteHTTPCode(201)&#13;
    WebWriteString(sResponse, "application/json")&#13;
    &#13;
EXCEPTION&#13;
    HandleAPIError("Erro ao criar registro: " + ExceptionInfo(), 500)&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// SetupDefaultValidations: Configura validações padrão&#13;
//——————————————————————————&#13;
PRIVATE PROCEDURE SetupDefaultValidations()&#13;
i is int&#13;
&#13;
```&#13;
// Adiciona validações automáticas baseadas na análise&#13;
FOR i = 1 TO ArrayCount(m_stFieldsInfo)&#13;
    // Campos obrigatórios&#13;
    IF m_stFieldsInfo[i].bMandatory THEN&#13;
        m_cValidation.AddRule(m_sTableName, m_stFieldsInfo[i].sName, "required", &#13;
                              "Campo '" + m_stFieldsInfo[i].sName + "' é obrigatório")&#13;
    END&#13;
    &#13;
    // Validações por tipo&#13;
    SWITCH m_stFieldsInfo[i].nType&#13;
        CASE hItemNumeric, hItemReal, hItemCurrency&#13;
            m_cValidation.AddRule(m_sTableName, m_stFieldsInfo[i].sName, "numeric",&#13;
                                  "Campo '" + m_stFieldsInfo[i].sName + "' deve ser numérico")&#13;
        &#13;
        CASE hItemDate&#13;
            m_cValidation.AddRule(m_sTableName, m_stFieldsInfo[i].sName, "date",&#13;
                                  "Campo '" + m_stFieldsInfo[i].sName + "' deve ser uma data válida")&#13;
    END&#13;
    &#13;
    // Campos de email (baseado no nome)&#13;
    IF Position(Lower(m_stFieldsInfo[i].sName), "email") &gt; 0 THEN&#13;
        m_cValidation.AddRule(m_sTableName, m_stFieldsInfo[i].sName, "email",&#13;
                              "Campo '" + m_stFieldsInfo[i].sName + "' deve ser um email válido")&#13;
    END&#13;
END&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// FUNÇÕES DE HOOK EXEMPLOS&#13;
//==============================================================================&#13;
&#13;
//——————————————————————————&#13;
// HookClienteBeforeCreate: Hook executado antes de criar cliente&#13;
//——————————————————————————&#13;
PROCEDURE HookClienteBeforeCreate(sTable is string, sOperation is string, sWhen is string, varData is Variant) : boolean&#13;
// Exemplo: Formatar dados antes de inserir&#13;
&#13;
```&#13;
// Converte nome para maiúsculo&#13;
IF VariantExists(varData, "nome") THEN&#13;
    varData.nome = Upper(varData.nome)&#13;
END&#13;
&#13;
// Formata telefone&#13;
IF VariantExists(varData, "telefone") THEN&#13;
    sTelefone is string = varData.telefone&#13;
    sTelefone = Replace(sTelefone, "(", "")&#13;
    sTelefone = Replace(sTelefone, ")", "")&#13;
    sTelefone = Replace(sTelefone, "-", "")&#13;
    sTelefone = Replace(sTelefone, " ", "")&#13;
    varData.telefone = sTelefone&#13;
END&#13;
&#13;
// Adiciona data de cadastro se não informada&#13;
IF NOT VariantExists(varData, "data_cadastro") THEN&#13;
    varData.data_cadastro = DateToString(Today(), "YYYY-MM-DD")&#13;
END&#13;
&#13;
Trace("🎣 Hook ClienteBeforeCreate executed - Data formatted")&#13;
RESULT True&#13;
```&#13;
&#13;
END&#13;
&#13;
//——————————————————————————&#13;
// HookProdutoAfterUpdate: Hook executado após atualizar produto&#13;
//——————————————————————————&#13;
PROCEDURE HookProdutoAfterUpdate(sTable is string, sOperation is string, sWhen is string, varData is Variant) : boolean&#13;
// Exemplo: Notificação quando estoque fica baixo&#13;
&#13;
```&#13;
IF VariantExists(varData, "estoque") AND VariantExists(varData, "estoque_minimo") THEN&#13;
    IF varData.estoque &lt;= varData.estoque_minimo THEN&#13;
        // Simula envio de notificação&#13;
        sNotificacao is string = StringBuild("⚠️ ALERTA: Produto '%1' com estoque baixo (%2 unidades)", &#13;
                                             varData.nome, varData.estoque)&#13;
        &#13;
        Trace(sNotificacao)&#13;
        &#13;
        // Aqui poderia enviar email, SMS, etc.&#13;
        // EnviarNotificacao("estoque_baixo", sNotificacao)&#13;
    END&#13;
END&#13;
&#13;
RESULT True&#13;
```&#13;
&#13;
END&#13;
&#13;
//==============================================================================&#13;
// EXEMPLO DE USO DO SERVIDOR AVANÇADO&#13;
//==============================================================================&#13;
&#13;
PROCEDURE TestarServidorAvancado()&#13;
Info(“🧪 Testando Servidor CRUD Avançado…”)&#13;
&#13;
```&#13;
TRY&#13;
    //----------------------------------------------------------------------&#13;
    // 1. CRIAR SERVIDOR AVANÇADO PARA CLIENTE&#13;
    //----------------------------------------------------------------------&#13;
    serverAvancado is CRestCRUDServerAdvanced("Cliente")&#13;
    &#13;
    // Configura recursos&#13;
    serverAvancado.EnableCache(True)&#13;
    serverAvancado.RequireAuth(True)&#13;
    &#13;
    // Registra hooks personalizados&#13;
    serverAvancado.m_cHooks.RegisterHook("Cliente", "CREATE", "BEFORE", "HookClienteBeforeCreate")&#13;
    &#13;
    // Adiciona validações customizadas&#13;
    serverAvancado.m_cValidation.AddRule("Cliente", "telefone", "custom", &#13;
                                         "Telefone deve ter pelo menos 10 dígitos", &#13;
                                         "ValidarTelefone")&#13;
    &#13;
    Info("✅ Servidor avançado configurado")&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 2. TESTAR AUTENTICAÇÃO&#13;
    //----------------------------------------------------------------------&#13;
    sTokenAdmin is string = serverAvancado.m_cAuth.AuthenticateUser("admin", "admin123")&#13;
    sTokenGuest is string = serverAvancado.m_cAuth.AuthenticateUser("guest", "guest")&#13;
    &#13;
    IF sTokenAdmin &lt;&gt; "" THEN&#13;
        Info("✅ Token Admin: " + Left(sTokenAdmin, 30) + "...")&#13;
    END&#13;
    &#13;
    IF sTokenGuest &lt;&gt; "" THEN&#13;
        Info("✅ Token Guest: " + Left(sTokenGuest, 30) + "...")&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 3. TESTAR VALIDAÇÕES&#13;
    //----------------------------------------------------------------------&#13;
    varCliente is Variant&#13;
    varCliente.nome = "João Silva"&#13;
    varCliente.email = "email_invalido"&#13;
    varCliente.telefone = "123" // Muito curto&#13;
    &#13;
    arrayErros is array of string&#13;
    IF NOT serverAvancado.m_cValidation.ValidateRecord("Cliente", varCliente, arrayErros) THEN&#13;
        sErros is string = "❌ Erros de validação encontrados:" + CR&#13;
        FOR i = 1 TO ArrayCount(arrayErros)&#13;
            sErros += "• " + arrayErros[i] + CR&#13;
        END&#13;
        Info(sErros)&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 4. TESTAR CACHE&#13;
    //----------------------------------------------------------------------&#13;
    serverAvancado.m_cCache.Set("teste_cache", "Dados de teste", 60)&#13;
    varCacheResult is Variant = serverAvancado.m_cCache.Get("teste_cache")&#13;
    &#13;
    IF VariantToJSON(varCacheResult) &lt;&gt; "null" THEN&#13;
        Info("✅ Cache funcionando: " + varCacheResult)&#13;
    END&#13;
    &#13;
    //----------------------------------------------------------------------&#13;
    // 5. INICIAR SERVIDOR&#13;
    //----------------------------------------------------------------------&#13;
    IF serverAvancado.StartServer(8095, "/api/v2") THEN&#13;
        Info("🚀 Servidor Avançado iniciado na porta 8095" + CR + CR +&#13;
             "Recursos habilitados:" + CR +&#13;
         &#13;
```&#13;
&#13;
--&#13;
Adriano José Boller&#13;
______________________________________________&#13;
Consultor e Representante Oficial da&#13;
PcSoft no Brasil&#13;
+55 (41) 99949 1800&#13;
adrianoboller@gmail.com&#13;
skype: adrianoboller&#13;
http://wxinformatica.com.br/</description><ttl>30</ttl><generator>WEBDEV</generator><language>pt_BR</language><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp</link><title>API Rest nunca foi tão fácil como agora</title><managingEditor>moderateur@pcsoft.fr (The moderator)</managingEditor><webMaster>webmaster@pcsoft.fr (The webmaster)</webMaster><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5198/read.awp</comments><pubDate>16 Aug 2025 03:31:49 Z</pubDate><description>{&#13;
  "baseURL": "http://localhost:8080",&#13;
  "apiKey": "SUA_CHAVE",&#13;
  "adminKey": "SUA_CHAVE_ADMIN_FORTE",&#13;
  "bearer": "SEU_TOK…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5198/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5198/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5197/read.awp</comments><pubDate>16 Aug 2025 03:31:08 Z</pubDate><description>{&#13;
  "info": {&#13;
    "name": "API v6.5 \u2013 Demo",&#13;
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collect…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5197/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5197/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5196/read.awp</comments><pubDate>16 Aug 2025 03:29:51 Z</pubDate><description># Client v6.5 (rev-2) – QuickStart&#13;
```wl&#13;
c is clsApiClientV65("http://localhost:8080")&#13;
c.SetAuthApiKey("SUA_CHAVE")&#13;
c.SetAdm…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5196/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5196/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5195/read.awp</comments><pubDate>16 Aug 2025 03:29:12 Z</pubDate><description>// ============================================================================&#13;
// clsApiClientV65 – Client Hardened v6.5 (rev-…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5195/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5195/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5194/read.awp</comments><pubDate>16 Aug 2025 03:28:45 Z</pubDate><description>// ============================================================================&#13;
// clsMetricsV65 – métricas simples JSON&#13;
// Ge…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5194/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5194/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5193/read.awp</comments><pubDate>16 Aug 2025 03:28:13 Z</pubDate><description>// ============================================================================&#13;
// clsInputValidatorV65 – validação e sanitizaç…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5193/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5193/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5192/read.awp</comments><pubDate>16 Aug 2025 03:27:47 Z</pubDate><description>// ============================================================================&#13;
// clsRateLimiterV65 – Rate limiting simples (I…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5192/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5192/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5191/read.awp</comments><pubDate>16 Aug 2025 03:27:13 Z</pubDate><description>// ============================================================================&#13;
// clsApiLoggerV65 – Logging &amp; Auditoria (HFSQL…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5191/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5191/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5190/read.awp</comments><pubDate>16 Aug 2025 03:26:39 Z</pubDate><description>// ============================================================================&#13;
// clsApiConfigV65 – Config externa (api_config…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5190/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5190/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5189/read.awp</comments><pubDate>16 Aug 2025 03:26:10 Z</pubDate><description>// ============================================================================&#13;
// clsOpenAPI_Importer_V65 – Importador univers…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5189/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5189/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item><item><author>Boller</author><category>pcsoft.br.windev</category><comments>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5188/read.awp</comments><pubDate>16 Aug 2025 03:25:35 Z</pubDate><description>Última versão 6.5&#13;
——————————————————&#13;
&#13;
// ============================================================================&#13;
// cls…</description><guid isPermaLink="true">https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5188/read.awp</guid><link>https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora-5188/read.awp</link><source url="https://forum.pcsoft.fr/en-US/pcsoft.br.windev/5184-api-rest-nunca-foi-tao-facil-como-agora/read.awp">API Rest nunca foi tão fácil como agora</source><title>Re: API Rest nunca foi tão fácil como agora</title></item></channel></rss>
