← back to drigonauta__opennow

Function bodies 45 total

All specs Real LLM only Function bodies
fixFirestore function · javascript · L24-L107 (84 LOC)
scripts/fix_firestore.js
async function fixFirestore() {
    console.log('🔥 Connecting to Firestore Project:', process.env.VITE_FIREBASE_PROJECT_ID);
    console.log('Scanning "business" collection for missing city/state...');

    try {
        const snapshot = await db.collection('business').get();
        if (snapshot.empty) {
            console.log('No businesses found.');
            return;
        }

        console.log(`Found ${snapshot.size} documents. Processing...`);

        let updatedCount = 0;
        let batch = db.batch();
        let batchCount = 0;
        const BATCH_SIZE = 400; // Limit is 500, playing safe

        for (const doc of snapshot.docs) {
            const b = doc.data();
            let needsUpdate = false;
            let updates = {};

            // Check if city/state is missing or clearly invalid (like numbers "15", "10" from previous bad script)
            // Or just check if we can parse better data

            // We only look at those with address
            if (b.
saveGooglePlaceToDb function · javascript · L2816-L2975 (160 LOC)
server/index_legacy.js
async function saveGooglePlaceToDb(place, defaultCity = '', defaultState = '') {
    const businessRef = db.collection('business');

    // 1. Parse Data FIRST (so we can update existing records if needed)
    let category = 'Outros';
    const types = place.types || [];
    if (types.includes('restaurant') || types.includes('food') || types.includes('meal_takeaway') || types.includes('bar') || types.includes('cafe')) category = 'Alimentação';
    else if (types.includes('bakery')) category = 'Padaria';
    else if (types.includes('pharmacy') || types.includes('drugstore')) category = 'Farmácia';
    else if (types.includes('health') || types.includes('doctor') || types.includes('hospital') || types.includes('dentist')) category = 'Saúde';
    else if (types.includes('gym')) category = 'Academia';
    else if (types.includes('supermarket') || types.includes('grocery_or_supermarket') || types.includes('convenience_store')) category = 'Mercado';
    else if (types.includes('shopping_mall
migrate function · javascript · L29-L82 (54 LOC)
server/scripts/migrate-country-code.js
async function migrate() {
  console.log('Starting migration: Add country_code to businesses...\n');

  const businessesRef = db.collection('businesses');
  const snapshot = await businessesRef.get();

  let updated = 0;
  let skipped = 0;
  let errors = 0;
  let batch = db.batch();
  let batchCount = 0;

  for (const doc of snapshot.docs) {
    const data = doc.data();

    // Skip if already has country_code
    if (data.country_code) {
      skipped++;
      continue;
    }

    try {
      batch.update(doc.ref, {
        country_code: 'BR',
        country: data.country || 'Brasil',
      });

      updated++;
      batchCount++;

      // Firestore batches have a 500 operation limit
      if (batchCount >= 499) {
        await batch.commit();
        console.log(`  Committed batch of ${batchCount} updates...`);
        batch = db.batch();
        batchCount = 0;
      }
    } catch (err) {
      console.error(`  Error updating doc ${doc.id}:`, err.message);
      errors++;
    }
 
seed function · javascript · L52-L76 (25 LOC)
server/seed.js
async function seed() {
    console.log('🌱 Seeding database...');

    for (const business of businesses) {
        try {
            const response = await fetch(API_URL, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(business)
            });

            const data = await response.json();

            if (response.ok) {
                console.log(`✅ Created: ${business.name}`);
            } else {
                console.error(`❌ Failed to create ${business.name}:`, data);
            }
        } catch (error) {
            console.error(`❌ Error creating ${business.name}:`, error.message);
        }
    }

    console.log('✨ Seeding complete!');
}
CollectionReference class · javascript · L26-L110 (85 LOC)
server/src/config/firebase_mock.js
class CollectionReference {
    constructor(name) {
        this.name = name;
        this.filters = [];
        this.limitVal = null;
        this.orderByVal = null;
    }

    doc(id) {
        return new DocumentReference(this.name, id);
    }

    where(field, op, value) {
        this.filters.push({ field, op, value });
        return this;
    }

    limit(n) {
        this.limitVal = n;
        return this;
    }

    orderBy(field, dir) {
        this.orderByVal = { field, dir };
        return this;
    }

    async get() {
        await db.read();
        let data = db.data[this.name] || [];

        // Apply filters
        for (const filter of this.filters) {
            if (filter.op === '==') {
                data = data.filter(item => item[filter.field] === filter.value);
            }
        }

        // Apply OrderBy
        if (this.orderByVal) {
            data.sort((a, b) => {
                const valA = a[this.orderByVal.field];
                const valB = b[
constructor method · javascript · L27-L32 (6 LOC)
server/src/config/firebase_mock.js
    constructor(name) {
        this.name = name;
        this.filters = [];
        this.limitVal = null;
        this.orderByVal = null;
    }
doc method · javascript · L34-L36 (3 LOC)
server/src/config/firebase_mock.js
    doc(id) {
        return new DocumentReference(this.name, id);
    }
Repobility · code-quality intelligence platform · https://repobility.com
where method · javascript · L38-L41 (4 LOC)
server/src/config/firebase_mock.js
    where(field, op, value) {
        this.filters.push({ field, op, value });
        return this;
    }
limit method · javascript · L43-L46 (4 LOC)
server/src/config/firebase_mock.js
    limit(n) {
        this.limitVal = n;
        return this;
    }
orderBy method · javascript · L48-L51 (4 LOC)
server/src/config/firebase_mock.js
    orderBy(field, dir) {
        this.orderByVal = { field, dir };
        return this;
    }
get method · javascript · L53-L94 (42 LOC)
server/src/config/firebase_mock.js
    async get() {
        await db.read();
        let data = db.data[this.name] || [];

        // Apply filters
        for (const filter of this.filters) {
            if (filter.op === '==') {
                data = data.filter(item => item[filter.field] === filter.value);
            }
        }

        // Apply OrderBy
        if (this.orderByVal) {
            data.sort((a, b) => {
                const valA = a[this.orderByVal.field];
                const valB = b[this.orderByVal.field];
                if (this.orderByVal.dir === 'desc') return valB - valA;
                return valA - valB;
            });
        }

        // Apply Limit
        if (this.limitVal) {
            data = data.slice(0, this.limitVal);
        }

        const docs = data.map(item => {
            const idField = ID_FIELDS[this.name] || 'id';
            return {
                id: item[idField],
                data: () => item,
                ref: new DocumentReference(this.name, item[idF
add method · javascript · L95-L109 (15 LOC)
server/src/config/firebase_mock.js
    async add(data) {
        await db.read();
        const collection = db.data[this.name] || [];
        const idField = ID_FIELDS[this.name] || 'id';

        // Generate random ID if not present (simple mock)
        const newId = Math.random().toString(36).substring(2, 15);
        const dataWithId = { ...data, [idField]: newId };

        collection.push(dataWithId);
        db.data[this.name] = collection;
        await db.write();

        return new DocumentReference(this.name, newId);
    }
DocumentReference class · javascript · L112-L183 (72 LOC)
server/src/config/firebase_mock.js
class DocumentReference {
    constructor(collectionName, id) {
        this.collectionName = collectionName;
        this.id = id;
    }

    getIdField() {
        return ID_FIELDS[this.collectionName] || 'id';
    }

    async set(data) {
        await db.read();
        const collection = db.data[this.collectionName] || [];
        const idField = this.getIdField();
        const index = collection.findIndex(item => item[idField] === this.id);

        // Ensure ID is in data
        const dataWithId = { ...data, [idField]: this.id };

        if (index > -1) {
            collection[index] = dataWithId;
        } else {
            collection.push(dataWithId);
        }
        db.data[this.collectionName] = collection;
        await db.write();
    }

    async get() {
        await db.read();
        const collection = db.data[this.collectionName] || [];
        const idField = this.getIdField();
        const item = collection.find(item => item[idField] === this.id);

        r
constructor method · javascript · L113-L116 (4 LOC)
server/src/config/firebase_mock.js
    constructor(collectionName, id) {
        this.collectionName = collectionName;
        this.id = id;
    }
getIdField method · javascript · L118-L120 (3 LOC)
server/src/config/firebase_mock.js
    getIdField() {
        return ID_FIELDS[this.collectionName] || 'id';
    }
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
set method · javascript · L122-L138 (17 LOC)
server/src/config/firebase_mock.js
    async set(data) {
        await db.read();
        const collection = db.data[this.collectionName] || [];
        const idField = this.getIdField();
        const index = collection.findIndex(item => item[idField] === this.id);

        // Ensure ID is in data
        const dataWithId = { ...data, [idField]: this.id };

        if (index > -1) {
            collection[index] = dataWithId;
        } else {
            collection.push(dataWithId);
        }
        db.data[this.collectionName] = collection;
        await db.write();
    }
get method · javascript · L140-L151 (12 LOC)
server/src/config/firebase_mock.js
    async get() {
        await db.read();
        const collection = db.data[this.collectionName] || [];
        const idField = this.getIdField();
        const item = collection.find(item => item[idField] === this.id);

        return {
            exists: !!item,
            data: () => item,
            ref: this
        };
    }
update method · javascript · L153-L167 (15 LOC)
server/src/config/firebase_mock.js
    async update(data) {
        await db.read();
        const collection = db.data[this.collectionName] || [];
        const idField = this.getIdField();
        const index = collection.findIndex(item => item[idField] === this.id);

        if (index > -1) {
            collection[index] = { ...collection[index], ...data };
            db.data[this.collectionName] = collection;
            await db.write();
        } else {
            // Firestore update fails if doc doesn't exist, but for mock we can ignore or throw
            console.warn(`Document ${this.id} not found in ${this.collectionName} for update`);
        }
    }
handleClickOutside function · typescript · L59-L63 (5 LOC)
src/components/CitySearch.tsx
        function handleClickOutside(event: MouseEvent) {
            if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
                setShowSuggestions(false);
            }
        }
ErrorBoundary class · typescript · L13-L67 (55 LOC)
src/components/ErrorBoundary.tsx
export class ErrorBoundary extends Component<Props, State> {
    public state: State = {
        hasError: false,
        error: null
    };

    public static getDerivedStateFromError(error: Error): State {
        return { hasError: true, error };
    }

    public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        if (import.meta.env.DEV) console.error('Uncaught error:', error, errorInfo);
    }

    public render() {
        if (this.state.hasError) {
            return (
                <div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center p-4">
                    <div className="bg-white rounded-xl shadow-lg p-8 max-w-md w-full text-center">
                        <div className="bg-red-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
                            <AlertTriangle className="w-8 h-8 text-red-600" />
                        </div>
                        <h1 className="text-2xl font-bold text-gray-90
getDerivedStateFromError method · typescript · L19-L21 (3 LOC)
src/components/ErrorBoundary.tsx
    public static getDerivedStateFromError(error: Error): State {
        return { hasError: true, error };
    }
componentDidCatch method · typescript · L23-L25 (3 LOC)
src/components/ErrorBoundary.tsx
    public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        if (import.meta.env.DEV) console.error('Uncaught error:', error, errorInfo);
    }
render method · typescript · L27-L66 (40 LOC)
src/components/ErrorBoundary.tsx
    public render() {
        if (this.state.hasError) {
            return (
                <div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center p-4">
                    <div className="bg-white rounded-xl shadow-lg p-8 max-w-md w-full text-center">
                        <div className="bg-red-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
                            <AlertTriangle className="w-8 h-8 text-red-600" />
                        </div>
                        <h1 className="text-2xl font-bold text-gray-900 mb-2">Ops! Algo deu errado.</h1>
                        <p className="text-gray-600 mb-6">
                            Desculpe, encontramos um erro inesperado. Tente recarregar a página.
                        </p>

                        {this.state.error && (
                            <div className="bg-gray-100 p-3 rounded text-left text-xs text-gray-500 font-mono mb-6 overflow-auto max-h-32">
        
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
RequireAdminAuth function · typescript · L4-L13 (10 LOC)
src/components/RequireAdminAuth.tsx
export default function RequireAdminAuth({ children }: { children: React.ReactNode }) {
    const token = localStorage.getItem('admin_token');
    const location = useLocation();

    if (!token) {
        return <Navigate to="/admin/login" state={{ from: location }} replace />;
    }

    return <>{children}</>;
}
useInactivityTracker function · typescript · L10-L40 (31 LOC)
src/hooks/useInactivityTracker.ts
export function useInactivityTracker(timeoutMs = 10000): boolean {
    const [isInactive, setIsInactive] = useState(false);
    const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

    useEffect(() => {
        const resetInactivity = () => {
            setIsInactive(false);
            if (timerRef.current) clearTimeout(timerRef.current);
            timerRef.current = setTimeout(() => {
                setIsInactive(true);
            }, timeoutMs);
        };

        // Listeners
        window.addEventListener('mousemove', resetInactivity);
        window.addEventListener('keydown', resetInactivity);
        window.addEventListener('click', resetInactivity);

        // Init
        resetInactivity();

        return () => {
            window.removeEventListener('mousemove', resetInactivity);
            window.removeEventListener('keydown', resetInactivity);
            window.removeEventListener('click', resetInactivity);
            if (timerRef.current) clea
getLocaleFromCountry function · typescript · L44-L46 (3 LOC)
src/i18n/countryLocaleMap.ts
export function getLocaleFromCountry(countryCode: string): string {
  return countryLocaleMap[countryCode.toUpperCase()] || 'en';
}
cn function · typescript · L4-L6 (3 LOC)
src/lib/utils.ts
export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs))
}
formatWhatsAppLink function · typescript · L8-L29 (22 LOC)
src/lib/utils.ts
export function formatWhatsAppLink(phone: string | undefined | null): string | null {
    if (!phone) return null;

    // Remove all non-numeric characters
    const cleaned = phone.replace(/\D/g, '');

    if (!cleaned) return null;

    // If it starts with 55 and is long enough (12-13 digits), assume it's already formatted
    if (cleaned.startsWith('55') && cleaned.length >= 12) {
        return `https://wa.me/${cleaned}`;
    }

    // If it's a standard Brazilian number (10-11 digits), prepend 55
    if (cleaned.length >= 10 && cleaned.length <= 11) {
        return `https://wa.me/55${cleaned}`;
    }

    // Fallback: return as is if it doesn't match known patterns but has digits
    // This handles cases where it might be an international number without +
    return `https://wa.me/${cleaned}`;
}
getGreeting function · typescript · L37-L42 (6 LOC)
src/lib/utils.ts
function getGreeting(): string {
    const hour = new Date().getHours();
    if (hour >= 5 && hour < 12) return GREETINGS.morning;
    if (hour >= 12 && hour < 18) return GREETINGS.afternoon;
    return GREETINGS.night;
}
createWhatsAppMessageLink function · typescript · L74-L139 (66 LOC)
src/lib/utils.ts
export function createWhatsAppMessageLink(
    phone: string | undefined | null,
    businessName: string,
    businessId: string,
    userName?: string | null,
    cityName?: string | null
): string | null {
    const formattedPhone = formatWhatsAppLink(phone);
    if (!formattedPhone) return null;

    const name = userName || "Visitante";
    const city = cityName || "Uberaba"; // Default fallback per request hints or keep generic? User said "cidade" variable. If null, safe fallback.
    const greeting = getGreeting();
    const linkTaaberto = `https://www.taaberto.com.br/empresa/${businessId}`; // Updated base URL as per request: https://www.taaberto.com.br

    // Logic to pick a random template avoiding the immediate last one
    // Note: This runs on client side, so localStorage is available.
    // However, during SSR or non-browser envs this might fail. We should check process/window.
    let templateIndex = 0;

    if (typeof window !== 'undefined') {
        const lastIndexK
AdminLogin function · typescript · L5-L117 (113 LOC)
src/pages/admin/AdminLogin.tsx
export default function AdminLogin() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [error, setError] = useState('');
    const [loading, setLoading] = useState(false);
    const navigate = useNavigate();

    const handleLogin = async (e: React.FormEvent) => {
        e.preventDefault();
        setError('');
        setLoading(true);

        try {
            // reCAPTCHA Removed per user request

            // Use relative URL for local development (proxy) or production same-domain
            const API_URL = ''; // Was 'https://opennow-...'

            const res = await fetch(`${API_URL}/api/admin/login`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'omit',
                body: JSON.stringify({ email, password })
            });

            const contentType = res.headers.get("content-type");
            if (!contentType || !contentType.
If a scraper extracted this row, it came from Repobility (https://repobility.com)
handleClickOutside function · typescript · L49-L53 (5 LOC)
src/pages/RegisterBusiness.tsx
        function handleClickOutside(event: MouseEvent) {
            if (locationWrapperRef.current && !locationWrapperRef.current.contains(event.target as Node)) {
                setShowLocationDropdown(false);
            }
        }
ApiError class · typescript · L5-L12 (8 LOC)
src/services/apiClient.ts
export class ApiError extends Error {
  status: number;
  constructor(status: number, message: string) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
  }
}
constructor method · typescript · L7-L11 (5 LOC)
src/services/apiClient.ts
  constructor(status: number, message: string) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
  }
getAuthToken function · typescript · L19-L23 (5 LOC)
src/services/apiClient.ts
async function getAuthToken(): Promise<string | null> {
  const user = auth.currentUser;
  if (!user) return null;
  return user.getIdToken();
}
apiClient function · typescript · L25-L57 (33 LOC)
src/services/apiClient.ts
export async function apiClient<T = unknown>(endpoint: string, options: ApiOptions = {}): Promise<T> {
  const { requireAuth = false, body, headers: customHeaders, ...rest } = options;

  const headers: Record<string, string> = {
    ...customHeaders as Record<string, string>,
  };

  if (body) {
    headers['Content-Type'] = 'application/json';
  }

  if (requireAuth) {
    const token = await getAuthToken();
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
  }

  const response = await fetch(`${API_BASE}${endpoint}`, {
    ...rest,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });

  if (!response.ok) {
    const errorText = await response.text().catch(() => response.statusText);
    throw new ApiError(response.status, errorText);
  }

  const text = await response.text();
  if (!text) return undefined as T;
  return JSON.parse(text) as T;
}
WebSocketService class · typescript · L3-L96 (94 LOC)
src/services/WebSocketService.ts
class WebSocketService {
  private ws: WebSocket | null = null;
  private subscribers = new Map<string, Set<MessageHandler>>();
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  private reconnectDelay = 3000;
  private url: string = '';
  private connected = false;

  connect(): void {
    if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) return;

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const host = window.location.host;
    this.url = `${protocol}//${host}`;

    try {
      this.ws = new WebSocket(this.url);

      this.ws.onopen = () => {
        this.connected = true;
        this.reconnectDelay = 3000;
      };

      this.ws.onmessage = (event: MessageEvent) => {
        try {
          const data = JSON.parse(event.data);
          const type = data.type as string;

          // Notify type-specific subscribers
          this.subscribers.get(type)?.forEach(handler => handler
connect method · typescript · L11-L52 (42 LOC)
src/services/WebSocketService.ts
  connect(): void {
    if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) return;

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const host = window.location.host;
    this.url = `${protocol}//${host}`;

    try {
      this.ws = new WebSocket(this.url);

      this.ws.onopen = () => {
        this.connected = true;
        this.reconnectDelay = 3000;
      };

      this.ws.onmessage = (event: MessageEvent) => {
        try {
          const data = JSON.parse(event.data);
          const type = data.type as string;

          // Notify type-specific subscribers
          this.subscribers.get(type)?.forEach(handler => handler(data));
          // Notify wildcard subscribers
          this.subscribers.get('*')?.forEach(handler => handler(data));
        } catch {
          // Ignore malformed messages
        }
      };

      this.ws.onclose = () => {
        this.connected = false;
        this.ws = null;
   
scheduleReconnect method · typescript · L54-L61 (8 LOC)
src/services/WebSocketService.ts
  private scheduleReconnect(): void {
    if (this.reconnectTimer) return;
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      this.connect();
    }, this.reconnectDelay);
    this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 30000);
  }
Repobility · code-quality intelligence platform · https://repobility.com
subscribe method · typescript · L63-L75 (13 LOC)
src/services/WebSocketService.ts
  subscribe(messageType: string, handler: MessageHandler): () => void {
    if (!this.subscribers.has(messageType)) {
      this.subscribers.set(messageType, new Set());
    }
    this.subscribers.get(messageType)!.add(handler);

    return () => {
      this.subscribers.get(messageType)?.delete(handler);
      if (this.subscribers.get(messageType)?.size === 0) {
        this.subscribers.delete(messageType);
      }
    };
  }
send method · typescript · L77-L81 (5 LOC)
src/services/WebSocketService.ts
  send(data: unknown): void {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }
disconnect method · typescript · L83-L91 (9 LOC)
src/services/WebSocketService.ts
  disconnect(): void {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    this.ws?.close();
    this.ws = null;
    this.connected = false;
  }
verifyRecentImports function · javascript · L13-L34 (22 LOC)
verify_import.cjs
async function verifyRecentImports() {
    console.log("Fetching last 5 businesses...");
    const snapshot = await db.collection('business')
        .orderBy('created_at', 'desc')
        .limit(5)
        .get();

    if (snapshot.empty) {
        console.log("No businesses found.");
        return;
    }

    snapshot.forEach(doc => {
        const data = doc.data();
        console.log("---------------------------------------------------");
        console.log(`Name: ${data.name}`);
        console.log(`Location: ${data.city} - ${data.state}, ${data.country || 'N/A'}`);
        console.log(`Phone Field: ${data.phone || '(empty)'}`);
        console.log(`WhatsApp Field: ${data.whatsapp || '(empty)'}`);
        console.log(`Created At: ${new Date(data.created_at).toLocaleString()}`);
    });
}
parseAddressOld function · javascript · L8-L21 (14 LOC)
verify_parsing.js
function parseAddressOld(formatted_address) {
    const parts = formatted_address.split(',').map(p => p.trim());
    const lastPart = parts[parts.length - 1];

    let parsedCity = '';
    let parsedState = '';
    let parsedCountry = lastPart;

    if (parts.length >= 3) {
        parsedCity = parts[parts.length - 3];
        parsedState = parts[parts.length - 2];
    }
    return { city: parsedCity, state: parsedState, country: parsedCountry };
}
parseAddressNew function · javascript · L23-L63 (41 LOC)
verify_parsing.js
function parseAddressNew(formatted_address) {
    const parts = formatted_address.split(',').map(p => p.trim());

    const ufs = ['AC', 'AL', 'AP', 'AM', 'BA', 'CE', 'DF', 'ES', 'GO', 'MA', 'MT', 'MS', 'MG', 'PA', 'PB', 'PR', 'PE', 'PI', 'RJ', 'RN', 'RS', 'RO', 'RR', 'SC', 'SP', 'SE', 'TO'];

    let parsedCity = '';
    let parsedState = '';
    let parsedCountry = 'Brasil';
    let foundBrazilPattern = false;

    for (const part of parts) {
        // FIX: Handle both hyphen and en-dash if possible, but let's test strict hyphen first as per code
        if (part && part.includes('-')) {
            const subParts = part.split('-').map(sp => sp.trim());
            if (subParts.length >= 2) {
                const potentialUF = subParts[subParts.length - 1].toUpperCase().substring(0, 2);

                if (ufs.includes(potentialUF) && /^[A-Z]{2}$/.test(potentialUF)) {
                    parsedState = potentialUF;
                    subParts.pop();
                    parsedCity