Estimado José,
Análisis del problema
El diagnóstico es correcto: FWMariaRowSet (y su clase padre FWRowSet) realizan ordenamiento en memoria del array ::aData usando ASort() de Harbour, que no respeta la collation latin1_spanish_ci de MySQL. Esto ocurre en varios puntos del código:
Causa raíz
1. Durante el SetUp() inicial, el RowSet parsea la cláusula ORDER BY del SQL y crea bloques de ordenamiento internos:
::aOrder := ::SortStr2Cols( AfterAtNum( "ORDER BY", Upper( ::Source ) ) )
::MakeSortBlocks( ::aOrder )
``` [0-cite-0](#0-cite-0)
Esto establece `::nOrder` y `::bSort`, lo que significa que el RowSet "sabe" que hay un orden activo.
**2. En `SortData()`**, se ejecuta el re-ordenamiento en memoria con comparación binaria:
```prg
ASort( ::aData, nFrom, nLast, ::bSort )
``` [0-cite-1](#0-cite-1)
**3. En `Requery()`**, después de recargar datos, si `::bSort` no está vacío, se llama a `::SortData()`, destruyendo el orden que trajo MySQL:
```prg
if ! Empty( ::bSort )
::SortData()
endif
``` [0-cite-2](#0-cite-2)
**4. `SetXBrowse()` asigna automáticamente `cSortOrder`** a cada columna (línea 988), lo que habilita el ordenamiento al hacer clic en cabeceras:
```prg
:cSortOrder := cCol
``` [0-cite-3](#0-cite-3)
**5. En `FWRowSet::SetOrder()`** (la clase padre), para campos de texto usa `StrICmp()` que es case-insensitive pero **no es locale-aware** (no respeta reglas de ñ, tildes, etc.):
```prg
ASort( ::aData, 1, nLimit, { |x,y| StrICmp( x[ nFld ], y[ nFld ] ) < 0 } )
``` [0-cite-4](#0-cite-4)
---
### Soluciones posibles
#### Solución 1: Evitar el re-ordenamiento interno (la más práctica)
Después de crear el RowSet y antes de asignarlo al XBrowse, limpie el orden interno para que el RowSet no re-ordene los datos que ya vienen correctamente ordenados desde MySQL:
```prg
oRs := oCn:RowSet( cSql ) // datos vienen con ORDER BY correcto
// Limpiar el orden interno para que no re-ordene
oRs:aOrder := { 0 }
oRs:bSort := nil
// Configurar XBrowse SIN autosort
XBROWSER oRs SETUP ( oBrw:lAutoSort := .f. )
Y después de SetXBrowse o CreateFromCode, limpie el cSortOrder de cada columna para evitar que al hacer clic en cabeceras se re-ordene:
// Después de CreateFromCode()
AEval( oBrw:aCols, { |oCol| oCol:cSortOrder := "" } )
Solución 2: No usar AUTOSORT y limpiar cSortOrder
Si construye el browse manualmente:
oRs := oCn:RowSet( cSql )
@ 0,0 XBROWSE oBrw OF oWnd ;
COLUMNS "CAMPO1", "CAMPO2" ;
LINES NOBORDER CELL
// NO usar AUTOSORT
// Limpiar cSortOrder de todas las columnas
AEval( oBrw:aCols, { |oCol| oCol:cSortOrder := "" } )
// Limpiar el sort interno del RowSet
oRs:aOrder := { 0 }
oRs:bSort := nil
oBrw:CreateFromCode()
Solución 3: Re-ordenar desde el servidor al hacer clic en cabecera
Si necesita que el usuario pueda cambiar el orden haciendo clic en cabeceras pero respetando la collation, puede interceptar el evento de ordenamiento para hacer un Requery con un nuevo ORDER BY en lugar de ordenar en memoria:
// Después de configurar el browse:
oRs:aOrder := { 0 }
oRs:bSort := nil
AEval( oBrw:aCols, { |oCol| oCol:cSortOrder := "" } )
// Si quiere re-habilitar sort por cabecera pero desde el servidor:
oBrw:bOnSort := { |oBrw, oCol| MyServerSort( oBrw, oCol ) }
Donde MyServerSort modifica el SQL con el nuevo ORDER BY y hace Requery() (asegurándose de limpiar bSort después del requery).
---
Resumen
El problema no es de XBrowse directamente, sino de FWMariaRowSet que:
- Parsea el ORDER BY del SQL y crea bloques de ordenamiento en memoria
- Usa ASort() con comparación binaria/StrICmp() que no respeta latin1_spanish_ci
- Re-aplica ese ordenamiento en Requery() y cuando se hace clic en cabeceras
La solución más directa es limpiar oRs:aOrder, oRs:bSort y los cSortOrder de las columnas del browse para que no se aplique ningún ordenamiento en memoria y se respete el orden que trae el ORDER BY de MySQL.