'Bancolombia', 'tipo' => self::detectarTipo($text), 'destinatario' => self::extraerPrimero($text, [ '/Bancolombia[:\s]+([A-ZÁÉÍÓÚÑÜ][A-Za-záéíóúñü\s]+?),\s+recibiste/u', '/Hola[,\s]+([A-ZÁÉÍÓÚÑÜ][A-Za-záéíóúñü\s]+?)[,\.]/u', '/estimado[a]?\s+([A-ZÁÉÍÓÚÑÜ][A-Za-záéíóúñü\s]+?)[,\.]/iu', ]), 'remitente' => self::extraerPrimero($text, [ '/transferencia\s+de\s+(.+?)\s+por\s+\$/iu', '/realiz[oó]\s+(?:un[a]?\s+)?(?:transferencia|pago)\s+de\s+\$[0-9,.]+\s+(?:a\s+tu\s+cuenta|para)\s+(.+?)[\.,]/iu', '/(?:pagador|remitente|enviado\s+por)[:\s]+([A-ZÁÉÍÓÚÑÜ][A-Za-záéíóúñü\s]+?)[\.,]/iu', '/de\s+([A-ZÁÉÍÓÚÑÜ][A-Za-záéíóúñü\s]{3,50}?)\s+(?:por\s+\$|a\s+tu)/iu', ]), 'valor' => self::extraerValor($text), 'cuenta' => self::extraerPrimero($text, [ '/cuenta\s+\*(\d+)/iu', '/cuenta\s+(?:de\s+ahorros|corriente)?\s*(?:No\.?\s*)?\*?(\d{4,})/iu', '/terminad[ao]\s+en\s+(\d{4})/iu', ]), 'llave' => self::extraerPrimero($text, [ '/llave\s+([@\w.\-]+)/iu', '/llave\s+(?:de\s+pago\s+)?([@\w.\-]+)/iu', '/([@][a-z0-9._\-]{3,})/iu', ]), 'fecha' => self::extraerPrimero($text, [ '/el\s+(\d{1,2}\/\d{1,2}\/\d{2,4})/iu', '/(\d{1,2}\/\d{1,2}\/\d{2,4})/u', '/(\d{1,2}\s+de\s+\w+\s+de\s+\d{4})/iu', '/fecha[:\s]+(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4})/iu', ]), 'hora' => self::extraerPrimero($text, [ '/a\s+las\s+(\d{1,2}:\d{2})/iu', '/hora[:\s]+(\d{1,2}:\d{2})/iu', '/(\d{1,2}:\d{2})\s*(?:a\.?m\.?|p\.?m\.?)/iu', '/(\d{2}:\d{2})(?::\d{2})?/u', ]), 'texto_original' => $text, ]; return $result; } // ───────────────────────────────────────────────────────── // Helpers privados // ───────────────────────────────────────────────────────── private static function esBancolombia(string $text): bool { if (stripos($text, 'bancolombia') === false) { return false; } // Al menos uno de estos indica una notificación transaccional $indicadores = ['transferencia', 'pago', 'recibiste', 'enviaste', 'credito', 'crédito', 'abono', 'consignacion', 'consignación', 'recibido', 'recibiste', 'valor', 'monto', '$']; foreach ($indicadores as $ind) { if (stripos($text, $ind) !== false) { return true; } } return false; } private static function detectarTipo(string $text): string { if (stripos($text, 'recibiste') !== false) { return 'transferencia_recibida'; } if (stripos($text, 'enviaste') !== false) { return 'transferencia_enviada'; } if (stripos($text, 'pago') !== false) { return 'pago'; } return 'notificacion'; } /** * Ejecuta un regex y devuelve el primer grupo capturado, trimmed. */ private static function extraer(string $text, string $pattern): string { if (preg_match($pattern, $text, $m)) { return trim($m[1]); } return ''; } /** * Prueba múltiples patrones y devuelve el primer match no-vacío. */ private static function extraerPrimero(string $text, array $patterns): string { foreach ($patterns as $pat) { $v = self::extraer($text, $pat); if ($v !== '') { return $v; } } return ''; } /** * Extrae el valor monetario como entero limpio sin decimales (ej: "1000"). * $1,000.00 → "1000" | $50.000,00 → "50000" */ private static function extraerValor(string $text): string { $patterns = [ '/por\s+\$\s*([0-9][0-9.,]*)/iu', '/valor[:\s]+\$?\s*([0-9][0-9.,]*)/iu', '/monto[:\s]+\$?\s*([0-9][0-9.,]*)/iu', '/recibiste[^$]*\$\s*([0-9][0-9.,]*)/iu', '/\$\s*([0-9][0-9.,]{2,})/u', ]; foreach ($patterns as $pat) { if (preg_match($pat, $text, $m)) { return self::normalizarValor(trim($m[1])); } } return ''; } /** * Convierte "1,000.00" o "1.000,00" a "1000" (entero limpio). */ private static function normalizarValor(string $raw): string { // Eliminar parte decimal: ,XX o .XX al final (pesos colombianos no tienen centavos reales) $raw = preg_replace('/[.,]\d{1,2}$/', '', $raw); // Eliminar separadores de miles (comas, puntos) return preg_replace('/[^0-9]/', '', $raw); } /** * Elimina completamente bloques