← back to geinersito__paris-luxe-journey

Function bodies 202 total

All specs Real LLM only Function bodies
HourlyQuote function · typescript · L6-L134 (129 LOC)
src/pages/hourly/Quote.tsx
export default function HourlyQuote() {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const intent = searchParams.get("intent");

  // Guard: intentionally strict to prevent hourly intent leakage
  // Only allow access when intent=hourly is explicitly set
  useEffect(() => {
    if (intent !== "hourly") {
      navigate("/hourly", { replace: true });
    }
  }, [intent, navigate]);

  const handleWhatsAppContact = () => {
    const message = encodeURIComponent(
      "Hello, I'm interested in hourly chauffeur service (mise à disposition) in Paris. Could you provide a quote?",
    );
    window.open(`https://wa.me/33123456789?text=${message}`, "_blank");
  };

  const handleEmailContact = () => {
    window.location.href =
      "mailto:[email protected]?subject=Hourly Service Quote Request";
  };

  if (intent !== "hourly") {
    return null;
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-primary/5 to-white py-12">
 
NotFound function · typescript · L4-L41 (38 LOC)
src/pages/NotFound.tsx
export default function NotFound() {
  const navigate = useNavigate();

  return (
    <div className="min-h-screen bg-background pt-28 pb-12 px-4">
      <div className="mx-auto max-w-2xl rounded-2xl border border-border bg-card p-8 text-center shadow-sm">
        <p className="text-sm font-semibold uppercase tracking-wide text-secondary">
          404
        </p>
        <h1 className="mt-3 text-3xl font-display font-bold text-primary">
          Page not found
        </h1>
        <p className="mt-3 text-muted-foreground">
          The page you requested is not available. You can continue to booking
          or go back to the home page.
        </p>

        <div className="mt-8 flex flex-col gap-3 sm:flex-row sm:justify-center">
          <Button
            type="button"
            onClick={() => navigate("/booking")}
            className="w-full sm:w-auto"
          >
            Go to Booking
          </Button>
          <Button
            type="button"
            vari
PrivacyPage function · typescript · L5-L157 (153 LOC)
src/pages/PrivacyPage.tsx
export default function PrivacyPage() {
  const { t } = useLanguage();

  return (
    <div className="min-h-screen bg-gradient-to-b from-white via-champagne/30 to-white">
      {/* Hero Section */}
      <section className="relative py-20 bg-gradient-to-br from-primary/10 via-champagne/20 to-white">
        <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
          <div className="text-center">
            <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-6">
              <Shield className="w-8 h-8 text-primary" />
            </div>
            <h1 className="text-4xl md:text-5xl lg:text-6xl font-display font-bold text-secondary mb-6">
              Privacy Policy
            </h1>
            <p className="text-lg md:text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
              Your privacy is important to us. Learn how we protect your data.
            </p>
            <p className="text-sm text-gray-500 mt-
TermsPage function · typescript · L5-L177 (173 LOC)
src/pages/TermsPage.tsx
export default function TermsPage() {
  const { t } = useLanguage();

  return (
    <div className="min-h-screen bg-gradient-to-b from-white via-champagne/30 to-white">
      {/* Hero Section */}
      <section className="relative py-20 bg-gradient-to-br from-primary/10 via-champagne/20 to-white">
        <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
          <div className="text-center">
            <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-6">
              <FileText className="w-8 h-8 text-primary" />
            </div>
            <h1 className="text-4xl md:text-5xl lg:text-6xl font-display font-bold text-secondary mb-6">
              Terms & Conditions
            </h1>
            <p className="text-lg md:text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
              Please read these terms carefully before using our services
            </p>
            <p className="text-sm text-gray-500 mt-4
processBookingEvent function · typescript · L52-L155 (104 LOC)
src/services/booking/BookingOrchestrator.ts
export async function processBookingEvent(
  booking: BookingContext,
  event: BookingEvent,
  metadata?: Record<string, any>
): Promise<OrchestrationResult> {
  console.log(`[Orchestrator] Processing event ${event} for booking ${booking.id}`);

  try {
    // 1. Ejecutar transición de estado con validaciones
    const transition = executeTransitionWithValidation(
      booking.status,
      event,
      {
        payment_mode: booking.payment_mode,
        pickup_datetime: booking.pickup_datetime,
        hold_status: booking.hold_status,
        partner_id: booking.partner_id,
      },
      {
        event,
        actor: metadata?.actor || 'system',
        ...metadata,
      }
    );

    if (!transition.success) {
      console.error(`[Orchestrator] Transition failed: ${transition.error}`);
      return {
        success: false,
        booking,
        error: transition.error,
      };
    }

    console.log(
      `[Orchestrator] State transition: ${transition.from_state} -> ${
processBookingEventSequence function · typescript · L167-L187 (21 LOC)
src/services/booking/BookingOrchestrator.ts
export async function processBookingEventSequence(
  booking: BookingContext,
  events: Array<{ event: BookingEvent; metadata?: Record<string, any> }>
): Promise<OrchestrationResult[]> {
  const results: OrchestrationResult[] = [];
  let currentBooking = booking;

  for (const { event, metadata } of events) {
    const result = await processBookingEvent(currentBooking, event, metadata);
    results.push(result);

    if (!result.success) {
      console.error(`[Orchestrator] Sequence stopped at event ${event}`);
      break;
    }

    currentBooking = result.booking;
  }

  return results;
}
getBookingById function · typescript · L20-L33 (14 LOC)
src/services/booking/BookingRepository.ts
export async function getBookingById(bookingId: string): Promise<BookingContext | null> {
  const { data, error } = await supabase
    .from('bookings_v312')
    .select('*')
    .eq('id', bookingId)
    .single();

  if (error) {
    console.error('[Repository] Error fetching booking:', error);
    return null;
  }

  return mapDatabaseToContext(data);
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
updateBookingStatus function · typescript · L38-L58 (21 LOC)
src/services/booking/BookingRepository.ts
export async function updateBookingStatus(
  bookingId: string,
  newStatus: BookingStatus,
  metadata?: Record<string, any>
): Promise<boolean> {
  const { error } = await supabase
    .from('bookings_v312')
    .update({
      status: newStatus,
      updated_at: new Date().toISOString(),
      ...metadata,
    })
    .eq('id', bookingId);

  if (error) {
    console.error('[Repository] Error updating booking:', error);
    return false;
  }

  return true;
}
logStateTransition function · typescript · L63-L90 (28 LOC)
src/services/booking/BookingRepository.ts
export async function logStateTransition(
  bookingId: string,
  fromState: BookingStatus,
  toState: BookingStatus,
  event: BookingEvent,
  metadata?: Record<string, any>
): Promise<boolean> {
  const { error } = await supabase
    .from('booking_state_logs')
    .insert({
      booking_id: bookingId,
      from_state: fromState,
      to_state: toState,
      event,
      metadata: {
        timestamp: new Date().toISOString(),
        ...metadata,
      },
      created_at: new Date().toISOString(),
    });

  if (error) {
    console.error('[Repository] Error logging transition:', error);
    return false;
  }

  return true;
}
getBookingStateHistory function · typescript · L95-L103 (9 LOC)
src/services/booking/BookingRepository.ts
export async function getBookingStateHistory(
  bookingId: string
): Promise<Array<{
  from_state: BookingStatus;
  to_state: BookingStatus;
  event: BookingEvent;
  created_at: string;
  metadata?: any;
}>> {
getBookingsByStatus function · typescript · L121-L138 (18 LOC)
src/services/booking/BookingRepository.ts
export async function getBookingsByStatus(
  status: BookingStatus,
  limit = 100
): Promise<BookingContext[]> {
  const { data, error } = await supabase
    .from('bookings_v312')
    .select('*')
    .eq('status', status)
    .limit(limit)
    .order('created_at', { ascending: false });

  if (error) {
    console.error('[Repository] Error fetching bookings:', error);
    return [];
  }

  return (data || []).map(mapDatabaseToContext);
}
getBookingsNeedingHold function · typescript · L143-L163 (21 LOC)
src/services/booking/BookingRepository.ts
export async function getBookingsNeedingHold(): Promise<BookingContext[]> {
  const now = new Date();
  const in24h = new Date(now.getTime() + 24 * 60 * 60 * 1000);
  const in25h = new Date(now.getTime() + 25 * 60 * 60 * 1000);

  const { data, error } = await supabase
    .from('bookings_v312')
    .select('*')
    .eq('payment_mode', 'flexible')
    .eq('status', 'confirmed')
    .is('hold_payment_intent_id', null)
    .gte('pickup_datetime', in24h.toISOString())
    .lt('pickup_datetime', in25h.toISOString());

  if (error) {
    console.error('[Repository] Error fetching bookings needing hold:', error);
    return [];
  }

  return (data || []).map(mapDatabaseToContext);
}
mapDatabaseToContext function · typescript · L168-L185 (18 LOC)
src/services/booking/BookingRepository.ts
function mapDatabaseToContext(data: any): BookingContext {
  return {
    id: data.id,
    status: data.status,
    payment_mode: data.payment_mode,
    pickup_datetime: data.pickup_datetime,
    hold_status: data.hold_status,
    partner_id: data.partner_id,
    customer_name: data.customer_name,
    customer_email: data.customer_email,
    customer_phone: data.customer_phone,
    route_key: data.route_key,
    vehicle_type: data.vehicle_type,
    total_price_cents: data.total_price_cents,
    hold_amount_cents: data.hold_amount_cents,
    confirmation_number: data.confirmation_number,
  };
}
replaceTemplateVariables function · typescript · L260-L269 (10 LOC)
src/services/notifications/NotificationService.ts
function replaceTemplateVariables(template: string, context: NotificationContext): string {
  let result = template;

  Object.keys(context).forEach((key) => {
    const value = context[key] || '';
    result = result.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
  });

  return result;
}
sendWhatsApp function · typescript · L274-L297 (24 LOC)
src/services/notifications/NotificationService.ts
async function sendWhatsApp(
  recipient: NotificationRecipient,
  message: string
): Promise<boolean> {
  const TWILIO_ACCOUNT_SID = import.meta.env?.VITE_TWILIO_ACCOUNT_SID;
  const TWILIO_AUTH_TOKEN = import.meta.env?.VITE_TWILIO_AUTH_TOKEN;
  const TWILIO_WHATSAPP_NUMBER = import.meta.env?.VITE_TWILIO_WHATSAPP_NUMBER;

  if (!TWILIO_ACCOUNT_SID || !recipient.phone) {
    console.warn('[WhatsApp] Missing credentials or phone number');
    return false;
  }

  try {
    // TODO: Implementar llamada a Twilio API
    console.log('[WhatsApp] Sending to:', recipient.phone);
    console.log('[WhatsApp] Message:', message);

    return true;
  } catch (error) {
    console.error('[WhatsApp] Error:', error);
    return false;
  }
}
Open data scored by Repobility · https://repobility.com
sendSMS function · typescript · L302-L325 (24 LOC)
src/services/notifications/NotificationService.ts
async function sendSMS(
  recipient: NotificationRecipient,
  message: string
): Promise<boolean> {
  const TWILIO_ACCOUNT_SID = import.meta.env?.VITE_TWILIO_ACCOUNT_SID;
  const TWILIO_AUTH_TOKEN = import.meta.env?.VITE_TWILIO_AUTH_TOKEN;
  const TWILIO_SMS_NUMBER = import.meta.env?.VITE_TWILIO_SMS_NUMBER;

  if (!TWILIO_ACCOUNT_SID || !recipient.phone) {
    console.warn('[SMS] Missing credentials or phone number');
    return false;
  }

  try {
    // TODO: Implementar llamada a Twilio API
    console.log('[SMS] Sending to:', recipient.phone);
    console.log('[SMS] Message:', message);

    return true;
  } catch (error) {
    console.error('[SMS] Error:', error);
    return false;
  }
}
sendEmail function · typescript · L330-L354 (25 LOC)
src/services/notifications/NotificationService.ts
async function sendEmail(
  recipient: NotificationRecipient,
  subject: string,
  body: string
): Promise<boolean> {
  const SENDGRID_API_KEY = import.meta.env?.VITE_SENDGRID_API_KEY;
  const SENDGRID_FROM_EMAIL = import.meta.env?.VITE_SENDGRID_FROM_EMAIL;

  if (!SENDGRID_API_KEY || !recipient.email) {
    console.warn('[Email] Missing credentials or email address');
    return false;
  }

  try {
    // TODO: Implementar llamada a SendGrid API
    console.log('[Email] Sending to:', recipient.email);
    console.log('[Email] Subject:', subject);
    console.log('[Email] Body:', body);

    return true;
  } catch (error) {
    console.error('[Email] Error:', error);
    return false;
  }
}
sendNotification function · typescript · L359-L364 (6 LOC)
src/services/notifications/NotificationService.ts
export async function sendNotification(
  event: BookingEvent,
  recipient: NotificationRecipient,
  context: NotificationContext,
  channels?: NotificationChannel[]
): Promise<{ success: boolean; channels: Record<NotificationChannel, boolean> }> {
PriceCalculationService.getInstance method · typescript · L11-L16 (6 LOC)
src/services/PriceCalculationService.ts
  public static getInstance(): PriceCalculationService {
    if (!PriceCalculationService.instance) {
      PriceCalculationService.instance = new PriceCalculationService();
    }
    return PriceCalculationService.instance;
  }
PriceCalculationService.loadFixedRoutes method · typescript · L23-L40 (18 LOC)
src/services/PriceCalculationService.ts
  private async loadFixedRoutes(): Promise<void> {
    // TODO: Create fixed_routes table in Supabase
    // For now, skip loading from database
    console.warn('Fixed routes table not available. Using pricing.ts constants only.');

    /* COMMENTED OUT: Requires fixed_routes table
    const { data, error } = await supabase
      .from('fixed_routes')
      .select('*');

    if (error) throw error;

    data?.forEach(route => {
      const key = this.generateRouteKey(route.origin_type, route.destination_type);
      this.fixedRoutes.set(key, route);
    });
    */
  }
PriceCalculationService.loadServiceLevels method · typescript · L42-L73 (32 LOC)
src/services/PriceCalculationService.ts
  private async loadServiceLevels(): Promise<void> {
    // TODO: Create service_levels table in Supabase
    // For now, use vehicles table as fallback
    try {
      const { data, error } = await supabase
        .from('vehicles')
        .select('*');

      if (error) throw error;

      data?.forEach(vehicle => {
        // Ensure features is an object, not an array
        const vehicleFeatures = Array.isArray(vehicle.features) ? {} : (vehicle.features || {});

        this.serviceLevels.set(vehicle.id, {
          id: vehicle.id,
          name: vehicle.name,
          description: { en: vehicle.description || '' },
          features: {
            wifi: (vehicleFeatures as any)?.wifi || false,
            water: (vehicleFeatures as any)?.water || false,
            meet_greet: (vehicleFeatures as any)?.meet_greet || false,
            flight_tracking: (vehicleFeatures as any)?.flight_tracking || false,
            ...vehicleFeatures
          },
          multiplier: 1.0 // D
PriceCalculationService.calculatePrice method · typescript · L79-L93 (15 LOC)
src/services/PriceCalculationService.ts
  public async calculatePrice(request: BookingPriceRequest): Promise<PriceCalculation> {
    try {
      // 1. Buscar ruta fija
      const fixedRoute = this.getFixedRoute(request);
      if (fixedRoute) {
        return this.calculateFixedRoutePrice(fixedRoute, request);
      }

      // 2. Si no es ruta fija, calcular precio dinámico
      return this.calculateDynamicPrice(request);
    } catch (error) {
      console.error('Error calculating price:', error);
      throw error;
    }
  }
PriceCalculationService.getFixedRoute method · typescript · L95-L101 (7 LOC)
src/services/PriceCalculationService.ts
  private getFixedRoute(request: BookingPriceRequest): any | null {
    const key = this.generateRouteKey(
      this.getLocationType(request.origin),
      this.getLocationType(request.destination)
    );
    return this.fixedRoutes.get(key) || null;
  }
Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
PriceCalculationService.getLocationType method · typescript · L103-L110 (8 LOC)
src/services/PriceCalculationService.ts
  private getLocationType(location: string): string {
    // Implementar lógica para determinar el tipo de ubicación
    if (location.toLowerCase().includes('cdg')) return 'cdg_airport';
    if (location.toLowerCase().includes('orly')) return 'orly_airport';
    if (location.toLowerCase().includes('beauvais')) return 'beauvais_airport';
    if (location.toLowerCase().includes('disney')) return 'disney';
    return 'other';
  }
PriceCalculationService.calculateFixedRoutePrice method · typescript · L112-L131 (20 LOC)
src/services/PriceCalculationService.ts
  private calculateFixedRoutePrice(route: any, request: BookingPriceRequest): PriceCalculation {
    const basePrice = request.passengers <= 3 ? route.base_price_1_3 : route.base_price_4_7;
    const serviceLevel = this.serviceLevels.get(request.serviceLevel);
    
    if (!serviceLevel) {
      throw new Error(`Service level ${request.serviceLevel} not found`);
    }

    return {
      basePrice,
      finalPrice: Math.round(basePrice * serviceLevel.multiplier),
      multiplier: serviceLevel.multiplier,
      currency: 'EUR',
      breakdown: {
        base: basePrice,
        serviceLevelAdjustment: basePrice * (serviceLevel.multiplier - 1),
        extras: 0
      }
    };
  }
PriceCalculationService.calculateDynamicPrice method · typescript · L133-L154 (22 LOC)
src/services/PriceCalculationService.ts
  private async calculateDynamicPrice(request: BookingPriceRequest): Promise<PriceCalculation> {
    // Aquí implementaremos el cálculo basado en Google Maps y zonas
    // Por ahora, usamos un precio base temporal
    const basePrice = 100; // Temporal
    const serviceLevel = this.serviceLevels.get(request.serviceLevel);
    
    if (!serviceLevel) {
      throw new Error(`Service level ${request.serviceLevel} not found`);
    }

    return {
      basePrice,
      finalPrice: Math.round(basePrice * serviceLevel.multiplier),
      multiplier: serviceLevel.multiplier,
      currency: 'EUR',
      breakdown: {
        base: basePrice,
        serviceLevelAdjustment: basePrice * (serviceLevel.multiplier - 1),
        extras: 0
      }
    };
  }
normalizeBookingStatus function · typescript · L45-L64 (20 LOC)
src/services/state-machine/BookingStateMachine.ts
export function normalizeBookingStatus(
  status: BookingStatus | LegacyBookingStatus
): BookingStatus {
  switch (status) {
    case "pending_payment":
      return "pending";
    case "partner_assigned":
      return "driver_assigned";
    case "failed":
      return "payment_failed";
    // hold_pending y hold_confirmed deberían usar FlexibleSubStatus
    // pero por compatibilidad temporal los mapeamos a estados existentes
    case "hold_pending":
      return "confirmed"; // Estado principal mientras hold está pendiente
    case "hold_confirmed":
      return "driver_assigned"; // Listo para asignar conductor
    default:
      return status as BookingStatus;
  }
}
isValidTransition function · typescript · L166-L173 (8 LOC)
src/services/state-machine/BookingStateMachine.ts
export function isValidTransition(
  currentState: BookingStatus | LegacyBookingStatus,
  event: BookingEvent,
): boolean {
  const normalizedState = normalizeBookingStatus(currentState);
  const allowedTransitions = STATE_TRANSITIONS[normalizedState];
  return event in allowedTransitions;
}
getNextState function · typescript · L178-L185 (8 LOC)
src/services/state-machine/BookingStateMachine.ts
export function getNextState(
  currentState: BookingStatus | LegacyBookingStatus,
  event: BookingEvent,
): BookingStatus | null {
  const normalizedState = normalizeBookingStatus(currentState);
  const allowedTransitions = STATE_TRANSITIONS[normalizedState];
  return allowedTransitions[event] || null;
}
executeTransition function · typescript · L190-L234 (45 LOC)
src/services/state-machine/BookingStateMachine.ts
export function executeTransition(
  currentState: BookingStatus | LegacyBookingStatus,
  event: BookingEvent,
  metadata?: Partial<TransitionMetadata>,
): TransitionResult {
  const normalizedState = normalizeBookingStatus(currentState);

  // Validar que la transición es válida
  if (!isValidTransition(normalizedState, event)) {
    return {
      success: false,
      from_state: normalizedState,
      to_state: normalizedState,
      event,
      error: `Invalid transition: ${normalizedState} -> ${event}`,
    };
  }

  // Obtener el siguiente estado
  const nextState = getNextState(normalizedState, event);

  if (!nextState) {
    return {
      success: false,
      from_state: normalizedState,
      to_state: normalizedState,
      event,
      error: `No next state defined for: ${normalizedState} -> ${event}`,
    };
  }

  // Transición exitosa
  return {
    success: true,
    from_state: normalizedState,
    to_state: nextState,
    event,
    metadata: {
      event,
      
validateBusinessRules function · typescript · L265-L321 (57 LOC)
src/services/state-machine/BookingStateMachine.ts
export function validateBusinessRules(
  currentState: BookingStatus | LegacyBookingStatus,
  event: BookingEvent,
  context: {
    payment_mode?: "prepaid" | "flexible";
    pickup_datetime?: string;
    flexible_sub_status?: FlexibleSubStatus;
    partner_id?: string;
  },
): BusinessValidation {
  const normalizedState = normalizeBookingStatus(currentState);

  // Regla 1: HOLD_CREATED solo es válido para modo flexible
  if (event === "HOLD_CREATED" && context.payment_mode !== "flexible") {
    return {
      valid: false,
      error: "Hold creation is only valid for flexible payment mode",
    };
  }

  // Regla 2: HOLD_CREATED solo dentro de ventana de 24h
  if (event === "HOLD_CREATED" && context.pickup_datetime) {
    const pickupTime = new Date(context.pickup_datetime).getTime();
    const now = Date.now();
    const hoursUntilPickup = (pickupTime - now) / (1000 * 60 * 60);

    if (hoursUntilPickup > 24 || hoursUntilPickup < 0) {
      return {
        valid: false,
        e
All rows above produced by Repobility · https://repobility.com
executeTransitionWithValidation function · typescript · L326-L356 (31 LOC)
src/services/state-machine/BookingStateMachine.ts
export function executeTransitionWithValidation(
  currentState: BookingStatus,
  event: BookingEvent,
  context: {
    payment_mode?: "prepaid" | "flexible";
    pickup_datetime?: string;
    hold_status?: string;
    partner_id?: string;
  },
  metadata?: Partial<TransitionMetadata>,
): TransitionResult {
  // Primero validar reglas de negocio
  const businessValidation = validateBusinessRules(
    currentState,
    event,
    context,
  );

  if (!businessValidation.valid) {
    return {
      success: false,
      from_state: currentState,
      to_state: currentState,
      event,
      error: `Business rule violation: ${businessValidation.error}`,
    };
  }

  // Si pasa validaciones, ejecutar transición normal
  return executeTransition(currentState, event, metadata);
}
getCorsHeaders function · typescript · L12-L22 (11 LOC)
supabase/functions/create-booking-payment/index.ts
function getCorsHeaders(req: Request): Record<string, string> {
  const origin = req.headers.get('Origin') ?? '';
  const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
  return {
    'Access-Control-Allow-Origin': allowedOrigin,
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
    'Access-Control-Max-Age': '86400',
    'Vary': 'Origin',
  };
}
classifyError function · typescript · L30-L69 (40 LOC)
supabase/functions/create-booking-payment/index.ts
function classifyError(err: unknown): RetryDecision {
  const anyErr = err as any;
  const code = anyErr?.code ?? anyErr?.error?.code;
  const status = anyErr?.status ?? anyErr?.error?.status;
  const message = String(anyErr?.message ?? anyErr?.error?.message ?? "");

  // Deterministic DB conflicts: NEVER RETRY
  if (code === "23505" || code === "23P01" || code === "23503" || code === "23514") {
    return { retry: false, status: 409, code: "DB_CONFLICT" };
  }
  if (status === 409) {
    return { retry: false, status: 409, code: "CONFLICT" };
  }
  if (status === 400) {
    return { retry: false, status: 400, code: "BAD_REQUEST" };
  }

  // Stripe errors: retry only on transient types
  const stripeType = anyErr?.type ?? anyErr?.error?.type;
  if (typeof stripeType === "string") {
    if (
      stripeType.includes("api_connection") ||
      stripeType.includes("rate_limit") ||
      stripeType.includes("api_error")
    ) {
      return { retry: true };
    }
    return { retry: fal
errorResponse function · typescript · L99-L116 (18 LOC)
supabase/functions/create-booking-payment/index.ts
function errorResponse(err: unknown, fallbackStatus = 500, corsHeaders: Record<string, string> = {}) {
  const anyErr = err as any;
  const decision = anyErr?.__retryDecision as RetryDecision | undefined;

  const status = decision?.status ?? fallbackStatus;
  const code = decision?.code ?? "ERROR";

  return new Response(
    JSON.stringify({
      ok: false,
      code,
      message: String(anyErr?.message ?? "error"),
      details: anyErr?.details ?? anyErr?.error?.details ?? null,
      dbCode: anyErr?.code ?? anyErr?.error?.code ?? null,
    }),
    { status, headers: { ...corsHeaders, 'Content-Type': 'application/json' } },
  );
}
generateSecureCode function · typescript · L214-L222 (9 LOC)
supabase/functions/create-exit-lead/index.ts
function generateSecureCode(length: number): string {
  const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // No ambiguous chars (0,O,1,I)
  const randomBytes = new Uint8Array(length)
  crypto.getRandomValues(randomBytes)
  
  return Array.from(randomBytes)
    .map(byte => chars[byte % chars.length])
    .join('')
}
sendWelcomeEmail function · typescript · L227-L266 (40 LOC)
supabase/functions/create-exit-lead/index.ts
async function sendWelcomeEmail(email: string, couponCode: string, language: string) {
  try {
    const { getEmailTemplate } = await import('./email-templates.ts')
    const { subject, html } = getEmailTemplate(couponCode, language)

    const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')

    if (!RESEND_API_KEY) {
      console.error('RESEND_API_KEY not configured')
      return
    }

    const response = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${RESEND_API_KEY}`
      },
      body: JSON.stringify({
        from: 'Paris Elite Services <[email protected]>',
        to: [email],
        subject: subject,
        html: html
      })
    })

    if (!response.ok) {
      const error = await response.text()
      console.error('Failed to send email:', error)
      throw new Error('Failed to send email')
    }

    const result = await response.json(
getCorsHeaders function · typescript · L10-L20 (11 LOC)
supabase/functions/get-stripe-key/index.ts
function getCorsHeaders(req: Request): Record<string, string> {
  const origin = req.headers.get('Origin') ?? '';
  const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
  return {
    'Access-Control-Allow-Origin': allowedOrigin,
    'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
    'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
    'Access-Control-Expose-Headers': 'Content-Length, X-JSON',
    'Vary': 'Origin',
  };
}
notifyAvailablePartners function · typescript · L144-L151 (8 LOC)
supabase/functions/partner-sla-job-v312/index.ts
async function notifyAvailablePartners(supabase: any, booking: any) {
  console.log(`[Notify] Sending notifications for booking ${booking.id}`);
  
  // TODO: Implementar lógica de notificación
  // - Buscar conductores disponibles en la zona
  // - Enviar notificación push/SMS/WhatsApp
  // - Registrar en tabla de notificaciones
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
autoAssignPartner function · typescript · L156-L163 (8 LOC)
supabase/functions/partner-sla-job-v312/index.ts
async function autoAssignPartner(supabase: any, booking: any) {
  console.log(`[Auto-Assign] Assigning partner for booking ${booking.id}`);
  
  // TODO: Implementar lógica de asignación automática
  // - Buscar conductor con mejor score/disponibilidad
  // - Asignar y actualizar estado
  // - Notificar al conductor y cliente
}
escalateToAdmin function · typescript · L168-L175 (8 LOC)
supabase/functions/partner-sla-job-v312/index.ts
async function escalateToAdmin(supabase: any, booking: any) {
  console.log(`[Escalate] Escalating booking ${booking.id} to admin`);
  
  // TODO: Implementar lógica de escalación
  // - Crear ticket de escalación
  // - Notificar a admin
  // - Marcar booking como urgente
}
emitBookingConfirmedToERP function · typescript · L11-L71 (61 LOC)
supabase/functions/_shared/erpIngest.ts
export async function emitBookingConfirmedToERP(args: {
  bookingId: string;
  erpIngestUrl: string;
  ingestSecret: string;
  paymentIntentId?: string;
}): Promise<void> {
  const { bookingId, erpIngestUrl, ingestSecret, paymentIntentId } = args;

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), ERP_INGEST_TIMEOUT_MS);

  try {
    const payload: Record<string, unknown> = {
      event_type: 'booking_confirmed',
      version: 'v1',
      booking_id: bookingId,
      idempotency_key: `booking_confirmed:${bookingId}:v1`,
    };
    if (paymentIntentId) {
      payload.payment_intent_id = paymentIntentId;
    }

    // Normalizar ERP_INGEST_URL para soportar origin base o ruta completa
    const base = erpIngestUrl.replace(/\/+$/, '');
    let endpoint = base;
    if (/\/functions\/v1\/ingest-booking-confirmed-v1$/.test(base)) {
      endpoint = base;
    } else if (/\/functions\/v1\/?$/.test(base)) {
      endpoint = `${base}/ingest-boo
emitBookingConfirmedToERP function · typescript · L219-L249 (31 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function emitBookingConfirmedToERP(
  booking: any,
  _paymentIntentId: string,
  _eventId?: string
): Promise<void> {
  try {
    const enabled = getEnvBool('ERP_INGEST_ENABLED', true);
    const erpIngestUrl = getEnv('ERP_INGEST_URL');
    const ingestSecret = getEnv('BOOKING_INGEST_SECRET');

    if (!enabled) {
      console.log('[webhook] ERP emit disabled via ERP_INGEST_ENABLED=false');
      return;
    }

    if (!erpIngestUrl || !ingestSecret) {
      console.log('[webhook] ERP emit skipped: missing env (ERP_INGEST_URL or BOOKING_INGEST_SECRET)');
      return;
    }

    // Call production-grade RGPD-safe implementation (no PII in payload)
    void emitToERP({
      bookingId: booking.id,
      erpIngestUrl,
      ingestSecret,
    paymentIntentId: _paymentIntentId,
    });
  } catch (err: any) {
    console.error('[webhook] ERP emit setup error:', { message: String(err?.message ?? err) });
  }
}
handlePaymentIntentSucceeded function · typescript · L254-L294 (41 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handlePaymentIntentSucceeded(supabase: any, paymentIntent: any, eventId?: string) {
  console.log('[webhook] PaymentIntent succeeded:', paymentIntent.id);

  const bookingId = paymentIntent.metadata?.booking_id;
  if (!bookingId) {
    console.error('[webhook] No booking_id in metadata');
    return;
  }

  // Actualizar booking a confirmed
  const update: Record<string, unknown> = {
    status: 'confirmed',
    payment_status: 'succeeded',
    updated_at: new Date().toISOString(),
  };
  if (paymentIntent.id) {
    update.payment_intent_id = paymentIntent.id;
  }
  const { error } = await supabase.from('bookings').update(update).eq('id', bookingId);

  if (error) {
    console.error('[webhook] Error actualizando booking:', error);
    throw error;
  }

  console.log('[webhook] Booking confirmado:', bookingId);

  // Obtener booking row completa para emit a ERP
  const { data: booking, error: fetchError } = await supabase
    .from('bookings')
    .select('*')
    .eq('i
handlePaymentIntentFailed function · typescript · L299-L326 (28 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handlePaymentIntentFailed(supabase: any, paymentIntent: any) {
  console.log('[webhook] PaymentIntent failed:', paymentIntent.id);

  const bookingId = paymentIntent.metadata?.booking_id;
  if (!bookingId) {
    console.error('[webhook] No booking_id in metadata');
    return;
  }

  // Actualizar booking a payment_failed
  const { error } = await supabase
    .from('bookings')
    .update({
      status: 'payment_failed',
      payment_status: 'failed',
      updated_at: new Date().toISOString(),
    })
    .eq('id', bookingId);

  if (error) {
    console.error('[webhook] Error actualizando booking:', error);
    throw error;
  }

  console.log('[webhook] Booking marcado como payment_failed:', bookingId);

  // TODO: Enviar notificación al cliente
}
handleSetupIntentSucceeded function · typescript · L331-L375 (45 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handleSetupIntentSucceeded(supabase: any, setupIntent: any, eventId?: string) {
  console.log('[webhook] SetupIntent succeeded:', setupIntent.id);

  const bookingId = setupIntent.metadata?.booking_id;
  if (!bookingId) {
    console.error('[webhook] No booking_id in metadata');
    return;
  }

  // Guardar payment_method_id
  const paymentMethodId = setupIntent.payment_method;

  const { error } = await supabase
    .from('bookings')
    .update({
      status: 'confirmed',
      payment_status: 'setup_succeeded',
      stripe_payment_method_id: paymentMethodId,
      flexible_sub_status: 'awaiting_hold',
      updated_at: new Date().toISOString(),
    })
    .eq('id', bookingId);

  if (error) {
    console.error('[webhook] Error actualizando booking:', error);
    throw error;
  }

  console.log('[webhook] Booking flexible confirmado:', bookingId);

  // Obtener booking row completa para emit a ERP
  const { data: booking, error: fetchError } = await supabase
    .fr
handleSetupIntentFailed function · typescript · L380-L406 (27 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handleSetupIntentFailed(supabase: any, setupIntent: any) {
  console.log('[webhook] SetupIntent failed:', setupIntent.id);

  const bookingId = setupIntent.metadata?.booking_id;
  if (!bookingId) {
    console.error('[webhook] No booking_id in metadata');
    return;
  }

  const { error } = await supabase
    .from('bookings')
    .update({
      status: 'payment_failed',
      payment_status: 'setup_failed',
      updated_at: new Date().toISOString(),
    })
    .eq('id', bookingId);

  if (error) {
    console.error('[webhook] Error actualizando booking:', error);
    throw error;
  }

  console.log('[webhook] Booking flexible fallido:', bookingId);

  // TODO: Enviar notificación al cliente
}
Open data scored by Repobility · https://repobility.com
handleHoldCreated function · typescript · L411-L436 (26 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handleHoldCreated(supabase: any, paymentIntent: any) {
  console.log('[webhook] Hold created:', paymentIntent.id);

  const bookingId = paymentIntent.metadata?.booking_id;
  if (!bookingId) {
    console.error('[webhook] No booking_id in metadata');
    return;
  }

  const { error } = await supabase
    .from('bookings')
    .update({
      hold_payment_intent_id: paymentIntent.id,
      hold_status: 'confirmed',
      flexible_sub_status: 'hold_confirmed',
      updated_at: new Date().toISOString(),
    })
    .eq('id', bookingId);

  if (error) {
    console.error('[webhook] Error actualizando booking:', error);
    throw error;
  }

  console.log('[webhook] Hold confirmado:', bookingId);
}
handleHoldCaptured function · typescript · L441-L473 (33 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handleHoldCaptured(supabase: any, charge: any) {
  console.log('[webhook] Hold captured:', charge.id);

  const paymentIntentId = charge.payment_intent;

  // Buscar booking por hold_payment_intent_id
  const { data: booking, error: findError } = await supabase
    .from('bookings')
    .select('id')
    .eq('hold_payment_intent_id', paymentIntentId)
    .single();

  if (findError || !booking) {
    console.error('[webhook] Booking no encontrado para hold:', paymentIntentId);
    return;
  }

  const { error } = await supabase
    .from('bookings')
    .update({
      hold_status: 'captured',
      hold_captured_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    })
    .eq('id', booking.id);

  if (error) {
    console.error('[webhook] Error actualizando booking:', error);
    throw error;
  }

  console.log('[webhook] Hold capturado para booking:', booking.id);
}
handlePaymentIntentCanceled function · typescript · L478-L519 (42 LOC)
supabase/functions/stripe-webhooks-v312/index.ts
async function handlePaymentIntentCanceled(supabase: any, paymentIntent: any) {
  console.log('[webhook] PaymentIntent canceled:', paymentIntent.id);

  const bookingId = paymentIntent.metadata?.booking_id;
  if (!bookingId) {
    // Puede ser un hold, buscar por hold_payment_intent_id
    const { data: booking } = await supabase
      .from('bookings')
      .select('id')
      .eq('hold_payment_intent_id', paymentIntent.id)
      .single();

    if (booking) {
      await supabase
        .from('bookings')
        .update({
          hold_status: 'cancelled',
          updated_at: new Date().toISOString(),
        })
        .eq('id', booking.id);

      console.log('[webhook] Hold cancelado para booking:', booking.id);
    }
    return;
  }

  const { error } = await supabase
    .from('bookings')
    .update({
      status: 'cancelled',
      payment_status: 'canceled',
      updated_at: new Date().toISOString(),
    })
    .eq('id', bookingId);

  if (error) {
    console.error('[
‹ prevpage 4 / 5next ›